2026-04-02 16:00:27 +02:00
// SPDX-License-Identifier: GPL-3.0-or-later
2026-04-02 16:25:49 +02:00
// Copyright (C) 2011 Andrea Mazzoleni
2011-03-04 20:59:21 +01:00
2026-06-24 00:15:15 +02:00
# include "os/portable.h"
2011-03-04 20:59:21 +01:00
2015-01-02 18:52:04 +01:00
# include "support.h"
2011-03-04 20:59:21 +01:00
# include "elem.h"
# include "state.h"
2015-03-02 23:01:15 +01:00
# include "parity.h"
2011-03-04 20:59:21 +01:00
2026-05-03 11:25:24 +02:00
static const char * es ( int err )
{
if ( is_hw ( err ) )
return " error_io " ;
else
return " error " ;
}
2011-03-04 20:59:21 +01:00
struct snapraid_scan {
2014-02-12 20:02:35 +01:00
struct snapraid_state * state ; /**< State used. */
struct snapraid_disk * disk ; /**< Disk used. */
2021-11-28 15:50:38 +01:00
thread_id_t thread ; /**< Thread used for scanning the disk */
int is_diff ; /**< If it's a diff command or a scanning */
int need_write ; /**< If a state write is required */
2011-05-24 21:49:15 +02:00
/**
* Counters of changes .
*/
2013-08-26 20:33:25 +02:00
unsigned count_equal ; /**< Files equal. */
2014-03-02 21:56:59 +01:00
unsigned count_move ; /**< Files with a different name, but equal inode, size and timestamp in the same disk. */
2013-08-26 20:33:25 +02:00
unsigned count_restore ; /**< Files with equal name, size and timestamp, but different inode. */
2015-04-30 22:05:53 +02:00
unsigned count_change ; /**< Files with same name, but different size and/or timestamp. */
unsigned count_copy ; /**< Files new, with same name size and timestamp of a file in a different disk. */
2026-03-14 17:48:30 +01:00
unsigned count_relocate ; /**< Like copy, but with the original disappeared. */
2013-08-26 20:33:25 +02:00
unsigned count_insert ; /**< Files new. */
2015-04-30 22:05:53 +02:00
unsigned count_remove ; /**< Files removed. */
2011-05-24 21:49:15 +02:00
2011-08-26 14:18:38 +02:00
tommy_list file_insert_list ; /**< Files to insert. */
tommy_list link_insert_list ; /**< Links to insert. */
2013-01-27 16:44:04 +01:00
tommy_list dir_insert_list ; /**< Dirs to insert. */
2026-01-10 16:30:35 +01:00
tommy_list local_filter_list ; /**< Filter list specific for the disk. */
2026-08-26 08:36:19 +02:00
struct fsidentity dir_identity ; /**< Effective scan root identity. */
2011-09-23 21:48:50 +02:00
/* nodes for data structures */
tommy_node node ;
2011-03-04 20:59:21 +01:00
} ;
2021-11-28 15:50:38 +01:00
static struct snapraid_scan * scan_alloc ( struct snapraid_state * state , struct snapraid_disk * disk , int is_diff )
{
struct snapraid_scan * scan ;
scan = malloc_nofail ( sizeof ( struct snapraid_scan ) ) ;
scan - > state = state ;
scan - > disk = disk ;
scan - > count_equal = 0 ;
scan - > count_move = 0 ;
scan - > count_copy = 0 ;
2026-03-14 17:48:30 +01:00
scan - > count_relocate = 0 ;
2021-11-28 15:50:38 +01:00
scan - > count_restore = 0 ;
scan - > count_change = 0 ;
scan - > count_remove = 0 ;
scan - > count_insert = 0 ;
tommy_list_init ( & scan - > file_insert_list ) ;
tommy_list_init ( & scan - > link_insert_list ) ;
tommy_list_init ( & scan - > dir_insert_list ) ;
2026-01-10 16:30:35 +01:00
tommy_list_init ( & scan - > local_filter_list ) ;
2026-08-26 08:36:19 +02:00
scan - > dir_identity . device = 0 ;
scan - > dir_identity . mnt_id = 0 ;
scan - > dir_identity . subvol = 0 ;
2021-11-28 15:50:38 +01:00
scan - > is_diff = is_diff ;
scan - > need_write = 0 ;
# if HAVE_THREAD
thread_mutex_init ( & disk - > stamp_mutex ) ;
# endif
return scan ;
}
2026-08-24 23:03:00 +02:00
static void scan_free ( void * void_scan )
2021-11-28 15:50:38 +01:00
{
2026-08-24 23:03:00 +02:00
struct snapraid_scan * scan = void_scan ;
2021-11-28 15:50:38 +01:00
# if HAVE_THREAD
thread_mutex_destroy ( & scan - > disk - > stamp_mutex ) ;
# endif
2026-01-10 16:30:35 +01:00
tommy_list_foreach ( & scan - > local_filter_list , filter_free ) ;
2021-11-28 15:50:38 +01:00
free ( scan ) ;
}
static void stamp_lock ( struct snapraid_disk * disk )
{
# if HAVE_THREAD
thread_mutex_lock ( & disk - > stamp_mutex ) ;
2021-11-29 17:31:52 +01:00
# else
( void ) disk ;
2021-11-28 15:50:38 +01:00
# endif
}
static void stamp_unlock ( struct snapraid_disk * disk )
{
# if HAVE_THREAD
thread_mutex_unlock ( & disk - > stamp_mutex ) ;
2021-11-29 17:31:52 +01:00
# else
( void ) disk ;
2021-11-28 15:50:38 +01:00
# endif
}
2013-01-27 18:58:48 +01:00
/**
2015-10-07 22:07:20 +02:00
* Remove the specified link from the data set .
2013-01-27 18:58:48 +01:00
*/
2015-10-08 22:19:52 +02:00
static void scan_link_remove ( struct snapraid_scan * scan , struct snapraid_link * slink )
2013-01-27 18:58:48 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 18:58:48 +01:00
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-01-27 18:58:48 +01:00
/* remove the file from the link containers */
2015-10-08 22:19:52 +02:00
tommy_hashdyn_remove_existing ( & disk - > linkset , & slink - > nodeset ) ;
tommy_list_remove_existing ( & disk - > linklist , & slink - > nodelist ) ;
2013-01-27 18:58:48 +01:00
/* deallocate */
2015-10-08 22:19:52 +02:00
link_free ( slink ) ;
2013-01-27 18:58:48 +01:00
}
/**
2015-10-07 22:07:20 +02:00
* Insert the specified link in the data set .
2013-01-27 18:58:48 +01:00
*/
2015-10-08 22:19:52 +02:00
static void scan_link_insert ( struct snapraid_scan * scan , struct snapraid_link * slink )
2013-01-27 18:58:48 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 18:58:48 +01:00
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-01-27 18:58:48 +01:00
/* insert the link in the link containers */
2015-10-08 22:19:52 +02:00
tommy_hashdyn_insert ( & disk - > linkset , & slink - > nodeset , slink , link_name_hash ( slink - > sub ) ) ;
tommy_list_insert_tail ( & disk - > linklist , & slink - > nodelist , slink ) ;
2013-01-27 18:58:48 +01:00
}
/**
2015-10-07 22:07:20 +02:00
* Process a symbolic link .
2013-01-27 18:58:48 +01:00
*/
2014-10-25 17:47:22 +02:00
static void scan_link ( struct snapraid_scan * scan , int is_diff , const char * sub , const char * linkto , unsigned link_flag )
2013-01-27 18:58:48 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_state * state = scan - > state ;
struct snapraid_disk * disk = scan - > disk ;
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink ;
2013-01-27 18:58:48 +01:00
/* check if the link already exists */
2015-10-08 22:19:52 +02:00
slink = tommy_hashdyn_search ( & disk - > linkset , link_name_compare_to_arg , sub , link_name_hash ( sub ) ) ;
if ( slink ) {
2013-01-27 18:58:48 +01:00
/* check if multiple files have the same name */
2015-10-08 22:19:52 +02:00
if ( link_flag_has ( slink , FILE_IS_PRESENT ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency for link '%s%s' \n " , disk - > dir , sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-27 18:58:48 +01:00
}
/* mark as present */
2015-10-08 22:19:52 +02:00
link_flag_set ( slink , FILE_IS_PRESENT ) ;
2013-01-27 18:58:48 +01:00
/* check if the link is not changed and it's of the same kind */
2015-10-08 22:19:52 +02:00
if ( strcmp ( slink - > linkto , linkto ) = = 0 & & link_flag = = link_flag_get ( slink , FILE_IS_LINK_MASK ) ) {
2013-01-27 18:58:48 +01:00
/* it's equal */
+ + scan - > count_equal ;
2026-01-14 10:35:57 +01:00
if ( state - > opt . gui_verbose ) {
2026-05-29 11:01:44 +02:00
log_tag ( " scan:equal:%s:%s \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2013-01-27 18:58:48 +01:00
}
} else {
/* it's an update */
2013-08-26 20:50:15 +02:00
/* we have to save the linkto/type */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-08-26 20:50:15 +02:00
+ + scan - > count_change ;
2026-05-29 11:01:44 +02:00
log_tag ( " scan:update:%s:%s \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " update %s \n " , fmt_term ( disk , slink - > sub ) ) ;
2013-01-27 18:58:48 +01:00
}
/* update it */
2015-10-08 22:19:52 +02:00
free ( slink - > linkto ) ;
slink - > linkto = strdup_nofail ( linkto ) ;
link_flag_let ( slink , link_flag , FILE_IS_LINK_MASK ) ;
2013-01-27 18:58:48 +01:00
}
2013-08-26 20:50:15 +02:00
/* nothing more to do */
return ;
2013-01-27 18:58:48 +01:00
} else {
/* create the new link */
+ + scan - > count_insert ;
2026-05-29 11:01:44 +02:00
log_tag ( " scan:add:%s:%s \n " , disk - > name , esc_tag ( sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " add %s \n " , fmt_term ( disk , sub ) ) ;
2013-01-27 18:58:48 +01:00
}
/* and continue to insert it */
}
/* insert it */
2015-10-08 22:19:52 +02:00
slink = link_alloc ( sub , linkto , link_flag ) ;
2013-01-27 18:58:48 +01:00
/* mark it as present */
2015-10-08 22:19:52 +02:00
link_flag_set ( slink , FILE_IS_PRESENT ) ;
2013-01-27 18:58:48 +01:00
/* insert it in the delayed insert list */
2015-10-08 22:19:52 +02:00
tommy_list_insert_tail ( & scan - > link_insert_list , & slink - > nodelist , slink ) ;
2013-01-27 18:58:48 +01:00
}
2011-03-04 20:59:21 +01:00
/**
2015-10-01 18:19:48 +02:00
* Insert the specified file in the parity .
2011-03-04 20:59:21 +01:00
*/
2015-10-01 18:19:48 +02:00
static void scan_file_allocate ( struct snapraid_scan * scan , struct snapraid_file * file )
2011-03-04 20:59:21 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_state * state = scan - > state ;
struct snapraid_disk * disk = scan - > disk ;
2015-10-01 18:19:48 +02:00
block_off_t i ;
block_off_t parity_pos ;
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2015-10-01 18:19:48 +02:00
/* allocate the blocks of the file */
parity_pos = disk - > first_free_block ;
for ( i = 0 ; i < file - > blockmax ; + + i ) {
2015-10-04 22:02:05 +02:00
struct snapraid_block * block ;
struct snapraid_block * over_block ;
2015-10-01 18:19:48 +02:00
snapraid_info info ;
/* increment the position until the first really free block */
2016-01-16 14:26:05 +01:00
while ( block_has_file ( fs_par2block_find ( disk , parity_pos ) ) )
2015-10-01 18:19:48 +02:00
+ + parity_pos ;
2015-10-04 22:02:05 +02:00
/* get block we are going to overwrite, if any */
2016-01-16 14:26:05 +01:00
over_block = fs_par2block_find ( disk , parity_pos ) ;
2015-10-04 22:02:05 +02:00
/* deallocate it */
2016-01-16 14:26:05 +01:00
if ( over_block ! = BLOCK_NULL )
2015-10-04 22:02:05 +02:00
fs_deallocate ( disk , parity_pos ) ;
2015-10-01 18:19:48 +02:00
/* get block specific info */
info = info_get ( & state - > infoarr , parity_pos ) ;
2015-10-04 22:02:05 +02:00
/* get the new block we are going to write */
block = fs_file2block_get ( file , i ) ;
2015-10-01 18:19:48 +02:00
/* if the file block already has an updated hash without rehash */
if ( block_has_updated_hash ( block ) & & ! info_get_rehash ( info ) ) {
/* the only possible case is for REP blocks */
assert ( block_state_get ( block ) = = BLOCK_STATE_REP ) ;
/* convert to a REP block */
block_state_set ( block , BLOCK_STATE_REP ) ;
/* and keep the hash as it's */
} else {
2015-10-04 22:02:05 +02:00
unsigned over_state ;
2015-10-01 18:19:48 +02:00
/* convert to a CHG block */
block_state_set ( block , BLOCK_STATE_CHG ) ;
2014-02-12 20:02:35 +01:00
2015-10-01 18:19:48 +02:00
/* state of the block we are going to overwrite */
2015-10-04 22:02:05 +02:00
over_state = block_state_get ( over_block ) ;
2015-10-01 18:19:48 +02:00
/* if the block is an empty one */
2015-10-04 22:02:05 +02:00
if ( over_state = = BLOCK_STATE_EMPTY ) {
2026-04-27 15:43:06 +02:00
/*
* The block was empty and filled with zeros
* set the hash to the special ZERO value
*/
2015-10-01 18:19:48 +02:00
hash_zero_set ( block - > hash ) ;
} else {
/* otherwise it's a DELETED one */
2015-10-04 22:02:05 +02:00
assert ( over_state = = BLOCK_STATE_DELETED ) ;
2015-10-01 18:19:48 +02:00
2026-04-26 15:59:58 +02:00
/*
* In this cases we don ' t know if the old state is still the one
* stored inside the parity , because after an aborted sync , the parity
* may be or may be not have been updated with the data that it ' s now
* deleted .
*
* We anyway keep the hash as the new sync will not assume it correct ,
* but the check / fix may be using it .
*
* For example :
* - One file is deleted
* - Sync aborted after updating the parity to the new state ,
* but without saving the content file representing this new state .
* - Another file is added again ( exactly here )
* with the hash of DELETED block not representing the real parity state
*/
2015-10-01 18:19:48 +02:00
/* copy the past hash of the block */
2016-04-07 21:39:02 +02:00
memcpy ( block - > hash , over_block - > hash , BLOCK_HASH_SIZE ) ;
2015-10-01 18:19:48 +02:00
}
}
/* store in the disk map, after invalidating all the other blocks */
2015-10-04 22:02:05 +02:00
fs_allocate ( disk , parity_pos , file , i ) ;
2015-10-01 18:19:48 +02:00
/* set the new free position */
disk - > first_free_block = parity_pos + 1 ;
}
/* insert in the list of contained files */
tommy_list_insert_tail ( & disk - > filelist , & file - > nodelist , file ) ;
}
/**
2015-11-20 18:29:53 +01:00
* Delete the specified file from the parity .
*
* Note that the parity remains allocated , but the blocks and the file are marked as DELETED .
2026-05-21 10:28:46 +02:00
*
* The file pointer remain in memory and it ' s inserted in the deleted set .
*
* It should not be deallocated , as the parity still references it , and other threads
* in the scanning process may reference it when searching for a copy in the " stampset " .
2015-10-01 18:19:48 +02:00
*/
static void scan_file_deallocate ( struct snapraid_scan * scan , struct snapraid_file * file )
{
struct snapraid_disk * disk = scan - > disk ;
2011-09-23 21:48:50 +02:00
block_off_t i ;
2011-03-22 23:23:45 +01:00
2015-10-01 18:19:48 +02:00
/* remove from the list of contained files */
tommy_list_remove_existing ( & disk - > filelist , & file - > nodelist ) ;
2011-03-22 23:23:45 +01:00
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* Here we are supposed to adjust the : : first_free_block position
* with the parity position we are deleting
* but we also know that we do only delayed insert , after all the deletion ,
* so at this point : : first_free_block is always at 0 , and we don ' t need to update it
*/
2015-11-20 18:29:53 +01:00
if ( disk - > first_free_block ! = 0 ) {
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency for first free position at '% " PRIu64 " ' deallocating file '%s' \n " , disk - > first_free_block , file - > sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2015-11-20 18:29:53 +01:00
/* LCOV_EXCL_STOP */
}
2011-03-04 20:59:21 +01:00
/* free all the blocks of the file */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < file - > blockmax ; + + i ) {
2015-10-04 22:02:05 +02:00
struct snapraid_block * block = fs_file2block_get ( file , i ) ;
2013-02-24 19:02:53 +01:00
unsigned block_state ;
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* In case we scan after an aborted sync ,
* we could get also intermediate states
*/
2013-02-24 19:02:53 +01:00
block_state = block_state_get ( block ) ;
switch ( block_state ) {
case BLOCK_STATE_BLK :
/* we keep the hash making it an "old" hash, because the parity is still containing data for it */
break ;
2013-10-27 20:59:37 +01:00
case BLOCK_STATE_CHG :
2026-04-26 15:59:58 +02:00
/*
* In this cases we don ' t know if the old state is still the one
* stored inside the parity , because after an aborted sync , the parity
* may be or may be not have been updated with the data that it ' s now
* deleted .
*
* We anyway keep the hash as the new sync will not assume it correct ,
* but the check / fix may be using it .
*
* For example :
* - One file is added
* - Sync aborted after updating the parity to the new state ,
* but without saving the content file representing this new state .
* - File is now deleted after the aborted sync
* - Sync again , deleting the blocks ( exactly here )
* with the hash of CHG block not representing the real parity state
*/
2013-02-24 19:02:53 +01:00
break ;
2014-02-01 17:57:26 +01:00
case BLOCK_STATE_REP :
/* we just don't know the old hash, and then we set it to invalid */
2015-10-01 23:22:21 +02:00
hash_invalid_set ( block - > hash ) ;
2014-02-01 17:57:26 +01:00
break ;
2014-09-27 00:32:39 +02:00
default :
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency in file '%s' deallocating block '% " PRIu64 " :% " PRIu64 " ' state %u \n " , file - > sub , i , file - > blockmax , block_state ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-24 19:02:53 +01:00
}
2015-10-01 23:22:21 +02:00
/* set the block as deleted */
block_state_set ( block , BLOCK_STATE_DELETED ) ;
2011-03-04 20:59:21 +01:00
}
2015-10-01 18:19:48 +02:00
2015-10-05 21:40:44 +02:00
/* mark the file as deleted */
file_flag_set ( file , FILE_IS_DELETED ) ;
2015-10-01 23:22:21 +02:00
/* insert it in the list of deleted blocks */
tommy_list_insert_tail ( & disk - > deletedlist , & file - > nodelist , file ) ;
2015-10-01 18:19:48 +02:00
}
static void scan_file_delayed_allocate ( struct snapraid_scan * scan , struct snapraid_file * file )
{
struct snapraid_state * state = scan - > state ;
struct snapraid_disk * disk = scan - > disk ;
/* if we sort for physical offsets we have to read them for new files */
if ( state - > opt . force_order = = SORT_PHYSICAL
& & file - > physical = = FILEPHY_UNREAD_OFFSET
) {
char path_next [ PATH_MAX ] ;
pathprint ( path_next , sizeof ( path_next ) , " %s%s " , disk - > dir , file - > sub ) ;
if ( filephy ( path_next , file - > size , & file - > physical ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: File physycal offset error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( file - > sub ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in getting the physical offset of file '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2015-10-01 18:19:48 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2026-07-16 23:22:15 +02:00
/* insert in the delayed allocation list */
2015-10-01 18:19:48 +02:00
tommy_list_insert_tail ( & scan - > file_insert_list , & file - > nodelist , file ) ;
2013-12-24 21:46:27 +01:00
}
/**
2015-10-07 22:07:20 +02:00
* Check if a file is completely formed of blocks with invalid parity ,
2014-02-16 08:59:59 +01:00
* and no rehash is tagged , and if it has at least one block .
2013-12-24 21:46:27 +01:00
*/
2015-10-01 18:19:48 +02:00
static int file_is_full_invalid_parity_and_stable ( struct snapraid_state * state , struct snapraid_disk * disk , struct snapraid_file * file )
2013-12-24 21:46:27 +01:00
{
block_off_t i ;
2014-02-16 08:59:59 +01:00
/* with no block, it never has an invalid parity */
2013-12-24 21:46:27 +01:00
if ( file - > blockmax = = 0 )
return 0 ;
/* check all blocks */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < file - > blockmax ; + + i ) {
2014-06-15 17:56:43 +02:00
snapraid_info info ;
2015-10-04 22:02:05 +02:00
struct snapraid_block * block = fs_file2block_get ( file , i ) ;
2015-10-08 18:41:01 +02:00
block_off_t parity_pos ;
2013-12-24 21:46:27 +01:00
2014-02-16 08:59:59 +01:00
/* exclude blocks with parity */
2014-02-01 23:32:53 +01:00
if ( ! block_has_invalid_parity ( block ) )
2013-12-24 21:46:27 +01:00
return 0 ;
2014-02-16 08:59:59 +01:00
2015-10-08 18:41:01 +02:00
/*
2016-01-15 19:50:21 +01:00
* Get the parity position .
*
* Note that here we expect to always have mapped
* parity , because kept files always have it .
*
2016-01-16 14:26:05 +01:00
* Anyway , checking for POS_NULL doesn ' t hurt .
2015-10-08 18:41:01 +02:00
*/
2016-01-16 14:26:05 +01:00
parity_pos = fs_file2par_find ( disk , file , i ) ;
2015-10-08 18:41:01 +02:00
2016-01-15 19:50:21 +01:00
/* if it's not mapped, it cannot have rehash */
2016-01-16 14:26:05 +01:00
if ( parity_pos ! = POS_NULL ) {
2016-01-15 19:50:21 +01:00
/* get block specific info */
info = info_get ( & state - > infoarr , parity_pos ) ;
2014-02-16 08:59:59 +01:00
2016-01-15 19:50:21 +01:00
/* if rehash fails */
if ( info_get_rehash ( info ) )
return 0 ;
}
2013-12-24 21:46:27 +01:00
}
return 1 ;
}
2014-02-12 20:02:35 +01:00
/**
2015-10-07 22:07:20 +02:00
* Check if a file is completely formed of blocks with an updated hash ,
2014-02-16 08:59:59 +01:00
* and no rehash is tagged , and if it has at least one block .
2014-02-12 20:02:35 +01:00
*/
2015-10-01 18:19:48 +02:00
static int file_is_full_hashed_and_stable ( struct snapraid_state * state , struct snapraid_disk * disk , struct snapraid_file * file )
2014-02-12 20:02:35 +01:00
{
block_off_t i ;
2014-02-16 08:59:59 +01:00
/* with no block, it never has a hash */
2014-02-12 20:02:35 +01:00
if ( file - > blockmax = = 0 )
return 0 ;
/* check all blocks */
2014-09-27 00:32:39 +02:00
for ( i = 0 ; i < file - > blockmax ; + + i ) {
2014-02-12 20:02:35 +01:00
snapraid_info info ;
2015-10-04 22:02:05 +02:00
struct snapraid_block * block = fs_file2block_get ( file , i ) ;
2015-10-07 22:44:39 +02:00
block_off_t parity_pos ;
2014-02-12 20:02:35 +01:00
2014-02-16 08:59:59 +01:00
/* exclude blocks without hash */
2014-02-12 20:02:35 +01:00
if ( ! block_has_updated_hash ( block ) )
return 0 ;
2015-10-07 22:44:39 +02:00
/*
2016-01-15 19:50:21 +01:00
* Get the parity position .
2015-10-07 22:44:39 +02:00
*
2016-01-15 19:50:21 +01:00
* Note that it ' s possible to have files
* not mapped into the parity , even if they
* have a valid hash .
*
* This happens for example , for ' copied ' files
* that have REP blocks , but not yet mapped .
*
* If there are multiple copies , it ' s also possible
* that such files are used as ' source ' to copy
* hashes , and then to get them inside this function .
2015-10-07 22:44:39 +02:00
*/
2016-01-16 14:26:05 +01:00
parity_pos = fs_file2par_find ( disk , file , i ) ;
2015-10-07 22:44:39 +02:00
2016-01-15 19:50:21 +01:00
/* if it's not mapped, it cannot have rehash */
2016-01-16 14:26:05 +01:00
if ( parity_pos ! = POS_NULL ) {
2016-01-15 19:50:21 +01:00
/* get block specific info */
info = info_get ( & state - > infoarr , parity_pos ) ;
2014-02-12 20:02:35 +01:00
2016-01-15 19:50:21 +01:00
/* exclude blocks needing a rehash */
if ( info_get_rehash ( info ) )
return 0 ;
}
2014-02-12 20:02:35 +01:00
}
return 1 ;
}
2014-10-04 10:27:23 +02:00
/**
* Refresh the file info .
*
* This is needed by Windows as the normal way to list directories may report not
* updated info . Only the GetFileInformationByHandle ( ) func , called file - by - file ,
2015-01-10 10:21:05 +01:00
* really ensures to return synced info .
2014-10-14 22:47:21 +02:00
*
* If this happens , we read also the physical offset , to avoid to read it later .
2014-10-04 10:27:23 +02:00
*/
2014-10-14 22:47:21 +02:00
static void scan_file_refresh ( struct snapraid_scan * scan , const char * sub , struct stat * st , uint64_t * physical )
2014-10-04 10:27:23 +02:00
{
# if HAVE_LSTAT_SYNC
2014-10-14 22:47:21 +02:00
struct snapraid_state * state = scan - > state ;
2014-10-04 10:27:23 +02:00
struct snapraid_disk * disk = scan - > disk ;
/* if the st_sync is not set, ensure to get synced info */
if ( st - > st_sync = = 0 ) {
char path_next [ PATH_MAX ] ;
struct stat synced_st ;
pathprint ( path_next , sizeof ( path_next ) , " %s%s " , disk - > dir , sub ) ;
2014-10-14 22:47:21 +02:00
/* if we sort for physical offsets we have to read them for new files */
if ( state - > opt . force_order = = SORT_PHYSICAL
& & * physical = = FILEPHY_UNREAD_OFFSET
) {
/* do nothing, leave the pointer to read the physical offset */
} else {
physical = 0 ; /* set the pointer to 0 to read nothing */
}
if ( lstat_sync ( path_next , & synced_st , physical ) ! = 0 ) {
2014-10-04 10:27:23 +02:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Stat error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in stat file '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2014-10-04 10:27:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( st - > st_mtime ! = synced_st . st_mtime
| | st - > st_mtimensec ! = synced_st . st_mtimensec
) {
2014-10-14 23:01:01 +02:00
# ifndef _WIN32
/*
* In Windows having different metadata is expected with open files
* because the metadata in the directory is updated only when the file
* is closed .
*
2020-05-02 14:21:40 +02:00
* The same happens for hardlinks that duplicate metadata .
2017-10-29 18:07:01 +01:00
* The link metadata is updated only when the link is opened .
* This extends also to st_size and st_nlink .
*
2014-10-14 23:01:01 +02:00
* See also :
* Why is the file size reported incorrectly for files that are still being written to ?
* http : //blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
*/
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Uncached time change error. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( sub ) ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Detected uncached time change from % " PRIu64 " .%09u to % " PRIu64 " .%09u for file '%s' \n " ,
2017-10-29 18:07:01 +01:00
( uint64_t ) st - > st_mtime , ( uint32_t ) st - > st_mtimensec , ( uint64_t ) synced_st . st_mtime , ( uint32_t ) synced_st . st_mtimensec , sub ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " It's better if you run SnapRAID without other processes running. \n " ) ;
2014-10-14 23:01:01 +02:00
# endif
2014-10-04 10:27:23 +02:00
st - > st_mtime = synced_st . st_mtime ;
st - > st_mtimensec = synced_st . st_mtimensec ;
}
if ( st - > st_size ! = synced_st . st_size ) {
2014-10-14 23:01:01 +02:00
# ifndef _WIN32
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Uncached size change error. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( sub ) ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Detected uncached size change from % " PRIu64 " to % " PRIu64 " for file '%s' \n " ,
2017-10-29 18:07:01 +01:00
( uint64_t ) st - > st_size , ( uint64_t ) synced_st . st_size , sub ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " It's better if you run SnapRAID without other processes running. \n " ) ;
2014-10-14 23:01:01 +02:00
# endif
2014-10-04 10:27:23 +02:00
st - > st_size = synced_st . st_size ;
}
2017-10-29 18:07:01 +01:00
if ( st - > st_nlink ! = synced_st . st_nlink ) {
# ifndef _WIN32
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Uncached nlink change error. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( sub ) ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Detected uncached nlink change from %u to %u for file '%s' \n " ,
2017-11-03 23:46:45 +01:00
( uint32_t ) st - > st_nlink , ( uint32_t ) synced_st . st_nlink , sub ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " It's better if you run SnapRAID without other processes running. \n " ) ;
2017-10-29 18:07:01 +01:00
# endif
st - > st_nlink = synced_st . st_nlink ;
}
2026-07-30 16:20:26 +02:00
if ( st - > st_ino ! = INODE_INVALID & & synced_st . st_ino ! = INODE_INVALID & & st - > st_ino ! = synced_st . st_ino ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Uncached inode change error. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( sub ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " DANGER! Detected uncached inode change from % " PRIu64 " to % " PRIu64 " for file '%s' \n " ,
2017-10-29 18:07:01 +01:00
( uint64_t ) st - > st_ino , ( uint64_t ) synced_st . st_ino , sub ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " It's better if you run SnapRAID without other processes running. \n " ) ;
2026-04-27 15:43:06 +02:00
/*
* At this point , it ' s too late to change inode
* and having inconsistent inodes may result to internal failures
* so , it ' s better to abort
*/
2014-10-04 10:27:23 +02:00
exit ( EXIT_FAILURE ) ;
}
}
# else
( void ) scan ;
( void ) sub ;
( void ) st ;
2014-10-18 16:52:28 +02:00
( void ) physical ;
2014-10-04 10:27:23 +02:00
# endif
}
2013-12-24 21:46:27 +01:00
/**
2026-08-17 23:57:56 +02:00
* Insert the file in the inode set .
2013-12-24 21:46:27 +01:00
*/
2026-08-17 23:57:56 +02:00
static void scan_file_inode_insert ( struct snapraid_scan * scan , struct snapraid_file * file )
2013-12-24 21:46:27 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2026-07-30 16:20:26 +02:00
if ( file - > inode ! = INODE_INVALID )
2015-10-01 18:19:48 +02:00
tommy_hashdyn_insert ( & disk - > inodeset , & file - > nodeset , file , file_inode_hash ( file - > inode ) ) ;
2026-08-17 23:57:56 +02:00
}
/**
* Insert the file in the path and stamp sets .
*/
static void scan_file_stamp_insert ( struct snapraid_scan * scan , struct snapraid_file * file )
{
struct snapraid_disk * disk = scan - > disk ;
2021-11-28 15:50:38 +01:00
stamp_lock ( disk ) ;
2015-10-01 18:19:48 +02:00
tommy_hashdyn_insert ( & disk - > pathset , & file - > pathset , file , file_path_hash ( file - > sub ) ) ;
tommy_hashdyn_insert ( & disk - > stampset , & file - > stampset , file , file_stamp_hash ( file - > size , file - > mtime_sec , file - > mtime_nsec ) ) ;
2021-11-28 15:50:38 +01:00
stamp_unlock ( disk ) ;
2013-12-24 21:46:27 +01:00
}
/**
2015-10-01 18:19:48 +02:00
* Remove the file from the data set .
*
* File is then deleted .
2013-12-24 21:46:27 +01:00
*/
2026-04-27 14:39:38 +02:00
static void scan_file_remove ( struct snapraid_scan * scan , struct snapraid_file * file , int keep_track )
2013-12-24 21:46:27 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2015-10-01 18:19:48 +02:00
/* remove the file from the containers */
2026-07-30 16:20:26 +02:00
if ( file - > inode ! = INODE_INVALID )
2013-08-26 20:33:25 +02:00
tommy_hashdyn_remove_existing ( & disk - > inodeset , & file - > nodeset ) ;
2026-07-30 16:20:26 +02:00
stamp_lock ( disk ) ;
2011-10-09 23:41:08 +02:00
tommy_hashdyn_remove_existing ( & disk - > pathset , & file - > pathset ) ;
2026-07-30 16:20:26 +02:00
tommy_hashdyn_remove_existing ( & disk - > stampset , & file - > stampset ) ;
stamp_unlock ( disk ) ;
2021-11-28 15:50:38 +01:00
2026-04-27 14:39:38 +02:00
/*
* Keep track of the removed file ( that won ' t be added later )
*
* This allows to record from where the parity was computed .
*/
if ( keep_track ) {
struct snapraid_dealloc * dealloc = dealloc_alloc ( scan - > state - > block_size , file - > sub , file - > size , file - > mtime_sec , file - > mtime_nsec ) ;
dealloc_import ( dealloc , file ) ;
/* insert the dealloc in the dealloc containers */
tommy_list_insert_tail ( & disk - > dealloclist , & dealloc - > nodelist , dealloc ) ;
}
2026-07-16 23:22:15 +02:00
/*
* Deallocate the file from the parity .
*
* This is safe to run unlocked because :
* 1. Modified files deallocations are deferred to Phase 2 ( mono - threaded phase ) .
* 2. Invalid parity files deallocated in Phase 1 are never selected by
* file_is_full_hashed_and_stable ( ) during copy - detection ( since they lack
* valid parity blocks ) .
*/
2015-10-01 18:19:48 +02:00
scan_file_deallocate ( scan , file ) ;
2011-03-04 20:59:21 +01:00
}
/**
2015-10-01 18:19:48 +02:00
* Keep the file as it ' s ( or with only a name / inode modification ) .
*
* If the file is kept , nothing has to be done .
*
* But if a file contains only blocks with invalid parity , it ' s reallocated to ensure
* to always minimize the space used in the parity .
*
* This could happen after a failed sync , when some other files are deleted ,
* and then new ones can be moved backward to fill the hole created .
2011-03-04 20:59:21 +01:00
*/
2015-10-01 18:19:48 +02:00
static void scan_file_keep ( struct snapraid_scan * scan , struct snapraid_file * file )
2011-03-04 20:59:21 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2015-10-01 18:19:48 +02:00
/* if the file is full invalid, schedule a reinsert at later stage */
if ( file_is_full_invalid_parity_and_stable ( scan - > state , disk , file ) ) {
2026-07-16 23:22:15 +02:00
2015-10-01 18:19:48 +02:00
struct snapraid_file * copy = file_dup ( file ) ;
2026-08-17 23:57:56 +02:00
file_flag_set ( copy , FILE_IS_REALLOC_NEW ) ;
2012-02-09 23:17:36 +01:00
2026-07-16 23:22:15 +02:00
/* insert in the delayed allocation list */
scan_file_delayed_allocate ( scan , copy ) ;
2014-02-01 23:32:53 +01:00
2026-07-16 23:22:15 +02:00
/* mark the file to be removed on Phase 2 */
file_flag_set ( file , FILE_IS_REALLOC_OLD ) ;
2011-03-04 20:59:21 +01:00
}
}
2011-03-16 23:52:10 +01:00
/**
2015-10-07 22:07:20 +02:00
* Process a file .
2011-03-16 23:52:10 +01:00
*/
2014-10-25 17:47:22 +02:00
static void scan_file ( struct snapraid_scan * scan , int is_diff , const char * sub , struct stat * st , uint64_t physical )
2011-03-04 20:59:21 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_state * state = scan - > state ;
struct snapraid_disk * disk = scan - > disk ;
2011-03-04 20:59:21 +01:00
struct snapraid_file * file ;
2014-02-12 20:02:35 +01:00
tommy_node * i ;
2014-10-04 10:27:23 +02:00
int is_original_file_size_different_than_zero ;
2014-12-09 21:25:17 +01:00
int is_file_already_present ;
2016-01-18 19:05:46 +01:00
data_off_t file_already_present_size ;
int64_t file_already_present_mtime_sec ;
int file_already_present_mtime_nsec ;
2014-12-09 21:25:17 +01:00
int is_file_reported ;
2026-07-16 23:22:15 +02:00
int is_file_modified ;
2013-08-27 23:02:12 +02:00
/*
2013-12-14 17:54:04 +01:00
* If the disk has persistent inodes and UUID , try a search on the past inodes ,
2013-09-25 23:20:07 +02:00
* to detect moved files .
2013-08-27 23:02:12 +02:00
*
2016-01-02 16:52:20 +01:00
* For persistent inodes we mean inodes that keep their values when the file - system
2013-09-25 23:20:07 +02:00
* is unmounted and remounted . This don ' t always happen .
2013-08-27 23:02:12 +02:00
*
* Cases found are :
* - Linux FUSE with exFAT driver from https : //code.google.com/p/exfat/.
* Inodes are reassigned at every mount restarting from 1 and incrementing .
2016-01-18 19:38:42 +01:00
* As worse , the exFAT support in FUSE doesn ' t use sub - second precision in timestamps
2013-08-27 23:02:12 +02:00
* making inode collision more easy ( exFAT by design supports 10 ms precision ) .
* - Linux VFAT kernel ( 3.2 ) driver . Inodes are fully reassigned at every mount .
*
2013-09-25 23:20:07 +02:00
* In such cases , to avoid possible random collisions , it ' s better to disable the moved
2013-09-29 21:01:31 +02:00
* file recognition .
2013-09-25 23:20:07 +02:00
*
2013-12-14 17:54:04 +01:00
* For persistent UUID we mean that it has the same UUID as before .
2016-01-02 16:52:20 +01:00
* Otherwise , if the UUID is changed , likely it ' s a new recreated file - system ,
2013-12-14 17:54:04 +01:00
* and then the inode have no meaning .
*
2016-01-02 16:52:20 +01:00
* Note that to disable the search by past inode , we do this implicitly
2013-12-14 17:54:04 +01:00
* removing all the past inode before searching for files .
* This ensures that no file is found with a past inode , but at the same time ,
* it allows to find new files with the same inode , to identify them as hardlinks .
2013-08-27 23:02:12 +02:00
*/
2014-04-03 23:02:43 +02:00
int has_past_inodes = ! disk - > has_volatile_inodes & & ! disk - > has_different_uuid & & ! disk - > has_unsupported_uuid ;
2013-08-27 23:02:12 +02:00
2026-04-27 15:43:06 +02:00
/*
* Always search with the new inode , in the all new inodes found until now ,
* with the eventual presence of also the past inodes
*/
2026-07-30 16:20:26 +02:00
uint64_t inode = st - > st_ino ; /* don't know the exact type of st_ino and we cannot pass it by pointer in the search */
if ( inode ! = INODE_INVALID )
file = tommy_hashdyn_search ( & disk - > inodeset , file_inode_compare_to_arg , & inode , file_inode_hash ( inode ) ) ;
else
file = 0 ;
2013-09-25 23:20:07 +02:00
2013-12-14 17:54:04 +01:00
/* identify moved files with past inodes and hardlinks with the new inodes */
2011-03-04 20:59:21 +01:00
if ( file ) {
2011-03-20 22:36:49 +01:00
/* check if the file is not changed */
2012-07-13 20:53:57 +02:00
if ( file - > size = = st - > st_size
& & file - > mtime_sec = = st - > st_mtime
2026-08-16 22:07:41 +02:00
& & file - > mtime_nsec = = STAT_NSEC ( st )
2012-07-13 20:53:57 +02:00
) {
2013-08-26 20:33:25 +02:00
/* check if multiple files have the same inode */
if ( file_flag_has ( file , FILE_IS_PRESENT ) ) {
2017-10-29 18:07:01 +01:00
/* if has_volatile_hardlinks is true, the nlink value is not reliable */
if ( ! disk - > has_volatile_hardlinks & & st - > st_nlink = = 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inode '% " PRIu64 " ' inconsistency for file '%s%s' already present \n " , ( uint64_t ) st - > st_ino , disk - > dir , sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-26 20:33:25 +02:00
}
2017-10-29 18:07:01 +01:00
2014-09-30 21:54:49 +02:00
/* it's a hardlink */
2014-10-25 17:47:22 +02:00
scan_link ( scan , is_diff , sub , file - > sub , FILE_IS_HARDLINK ) ;
2014-09-30 21:54:49 +02:00
return ;
2013-08-26 20:33:25 +02:00
}
2011-03-04 20:59:21 +01:00
/* mark as present */
2011-04-28 23:09:19 +02:00
file_flag_set ( file , FILE_IS_PRESENT ) ;
2011-03-29 23:55:47 +02:00
if ( strcmp ( file - > sub , sub ) ! = 0 ) {
2011-10-09 23:41:08 +02:00
/* if the path is different, it means a moved file with the same inode */
2013-08-26 20:33:25 +02:00
+ + scan - > count_move ;
2011-10-09 23:41:08 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " scan:move:%s:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) , esc_tag ( sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " move %s -> %s \n " , fmt_term ( disk , file - > sub ) , fmt_term ( disk , sub ) ) ;
2011-06-20 20:35:04 +02:00
}
2013-08-26 20:33:25 +02:00
/* remove from the name set */
2011-10-09 23:41:08 +02:00
tommy_hashdyn_remove_existing ( & disk - > pathset , & file - > pathset ) ;
2026-07-06 16:19:21 +02:00
/*
* Protect the file - > sub mutation because other threads may concurrently
* search the stampset and read this file ' s name string inside
* file_namestamp_compare / file_pathstamp_compare , or when doing
* a pathcpy ( ) during copy detection .
*/
stamp_lock ( disk ) ;
2013-02-26 21:26:45 +01:00
file_rename ( file , sub ) ;
2026-07-06 16:19:21 +02:00
stamp_unlock ( disk ) ;
2011-03-29 23:55:47 +02:00
2013-08-26 20:33:25 +02:00
/* reinsert in the name set */
2011-10-09 23:41:08 +02:00
tommy_hashdyn_insert ( & disk - > pathset , & file - > pathset , file , file_path_hash ( file - > sub ) ) ;
/* we have to save the new name */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2011-03-29 23:55:47 +02:00
} else {
/* otherwise it's equal */
+ + scan - > count_equal ;
2011-06-20 20:35:04 +02:00
2026-01-14 10:35:57 +01:00
if ( state - > opt . gui_verbose ) {
2026-05-29 11:01:44 +02:00
log_tag ( " scan:equal:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
2011-06-20 20:35:04 +02:00
}
2011-03-29 23:55:47 +02:00
}
2011-05-24 21:49:15 +02:00
2013-12-24 21:46:27 +01:00
/* mark the file as kept */
2014-02-12 20:02:35 +01:00
scan_file_keep ( scan , file ) ;
2013-12-24 21:46:27 +01:00
2011-05-24 21:49:15 +02:00
/* nothing more to do */
2011-03-04 20:59:21 +01:00
return ;
2013-08-26 20:33:25 +02:00
}
2017-10-27 22:56:00 +02:00
/*
* Here the file matches the inode , but not the other info
*
* It could be a modified file with the same name ,
* or a restored / copied file that get assigned a previously used inode ,
* or a file - system with not persistent inodes .
*
* In NTFS it could be also a hardlink , because in NTFS
* hardlink don ' t share the same directory information ,
* like attribute and time .
*
* For example :
* C : > echo A > A
* C : > mklink / H B A
* . . . wait one minute
* C : > echo AAAAAAAAAAAAAA > A
* C : > dir
* . . . both time and size of A and B don ' t match !
*/
2013-08-26 20:33:25 +02:00
if ( file_flag_has ( file , FILE_IS_PRESENT ) ) {
2017-10-29 18:07:01 +01:00
/* if has_volatile_hardlinks is true, the nlink value is not reliable */
if ( ! disk - > has_volatile_hardlinks & & st - > st_nlink = = 1 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inode '% " PRIu64 " ' inconsistency for files '%s%s' and '%s%s' with same inode but different attributes: size % " PRIu64 " ?% " PRIu64 " , sec % " PRIu64 " ?% " PRIu64 " , nsec %d?%d \n " ,
2017-10-29 18:07:01 +01:00
file - > inode , disk - > dir , sub , disk - > dir , file - > sub ,
file - > size , ( uint64_t ) st - > st_size ,
file - > mtime_sec , ( uint64_t ) st - > st_mtime ,
file - > mtime_nsec , STAT_NSEC ( st ) ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
/* LCOV_EXCL_START */
/* suppose it's hardlink with not synced metadata */
2017-10-27 22:56:00 +02:00
scan_link ( scan , is_diff , sub , file - > sub , FILE_IS_HARDLINK ) ;
return ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-26 20:33:25 +02:00
}
2026-04-27 15:43:06 +02:00
/*
* Assume a previously used inode , it ' s the worst case
* and we handle it removing the duplicate stored inode .
* If the file is found by name later , it will have the inode restored ,
* otherwise , it will get removed
*/
2013-08-26 20:33:25 +02:00
2026-07-30 16:20:26 +02:00
/* here the inode must be present */
if ( file - > inode = = INODE_INVALID ) {
/* LCOV_EXCL_START */
log_fatal ( EINTERNAL , " Internal inconsistency in inode for file '%s%s' as unexpected missing \n " , disk - > dir , sub ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2013-08-26 20:33:25 +02:00
/* remove from the inode set */
tommy_hashdyn_remove_existing ( & disk - > inodeset , & file - > nodeset ) ;
2026-07-30 16:20:26 +02:00
/* clear the inode */
file - > inode = INODE_INVALID ;
2013-08-26 20:33:25 +02:00
/* go further to find it by name */
}
2014-12-09 21:25:17 +01:00
/* initialize for later overwrite */
is_file_reported = 0 ;
2026-07-16 23:22:15 +02:00
is_file_modified = 0 ;
2014-10-04 10:27:23 +02:00
is_original_file_size_different_than_zero = 0 ;
2014-03-02 21:56:59 +01:00
2014-06-05 23:06:13 +02:00
/* then try finding it by name */
2014-02-12 20:02:35 +01:00
file = tommy_hashdyn_search ( & disk - > pathset , file_path_compare_to_arg , sub , file_path_hash ( sub ) ) ;
2014-12-09 21:25:17 +01:00
/* keep track if the file already exists */
is_file_already_present = file ! = 0 ;
if ( is_file_already_present ) {
2013-08-26 20:33:25 +02:00
/* if the file is without an inode */
2026-07-30 16:20:26 +02:00
if ( file - > inode = = INODE_INVALID ) {
2013-08-26 20:33:25 +02:00
/* set it now */
file - > inode = st - > st_ino ;
2026-07-30 16:20:26 +02:00
/* insert in the set if valid */
if ( file - > inode ! = INODE_INVALID )
tommy_hashdyn_insert ( & disk - > inodeset , & file - > nodeset , file , file_inode_hash ( file - > inode ) ) ;
2011-03-04 20:59:21 +01:00
} else {
2013-08-26 20:33:25 +02:00
/* here the inode has to be different, otherwise we would have found it before */
if ( file - > inode = = st - > st_ino ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-07-30 16:20:26 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency in inode '% " PRIu64 " ' for file '%s%s' as unexpected matching \n " , file - > inode , disk - > dir , sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-17 09:54:02 +01:00
}
2013-08-26 20:33:25 +02:00
}
2011-03-18 22:38:11 +01:00
2013-08-26 20:33:25 +02:00
/* for sure it cannot be already present */
if ( file_flag_has ( file , FILE_IS_PRESENT ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency in path for file '%s%s' matching and already present \n " , disk - > dir , sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-26 20:33:25 +02:00
}
2011-07-27 18:51:07 +02:00
2013-08-26 20:33:25 +02:00
/* check if the file is not changed */
if ( file - > size = = st - > st_size
& & file - > mtime_sec = = st - > st_mtime
2026-08-16 22:07:41 +02:00
& & file - > mtime_nsec = = STAT_NSEC ( st )
2013-08-26 20:33:25 +02:00
) {
/* mark as present */
file_flag_set ( file , FILE_IS_PRESENT ) ;
2013-12-14 17:54:04 +01:00
/* if when processing the disk we used the past inodes values */
2026-07-30 16:20:26 +02:00
if ( has_past_inodes & & file - > inode ! = INODE_INVALID & & st - > st_ino ! = INODE_INVALID ) {
2026-04-27 15:43:06 +02:00
/*
* If persistent inodes are supported , we are sure that the inode number
* is now different , because otherwise the file would have been found
* when searching by inode .
* if the inode is different , it means a rewritten file with the same path
* like when restoring a backup that restores also the timestamp
*/
2013-09-30 22:42:33 +02:00
+ + scan - > count_restore ;
2013-08-26 20:33:25 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " scan:restore:%s:%s \n " , disk - > name , esc_tag ( sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " restore %s \n " , fmt_term ( disk , sub ) ) ;
2013-09-30 22:42:33 +02:00
}
2013-08-26 20:33:25 +02:00
2013-09-30 22:42:33 +02:00
/* remove from the inode set */
tommy_hashdyn_remove_existing ( & disk - > inodeset , & file - > nodeset ) ;
/* save the new inode */
file - > inode = st - > st_ino ;
/* reinsert in the inode set */
tommy_hashdyn_insert ( & disk - > inodeset , & file - > nodeset , file , file_inode_hash ( file - > inode ) ) ;
/* we have to save the new inode */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-09-30 22:42:33 +02:00
} else {
2026-04-27 15:43:06 +02:00
/*
* Otherwise it ' s the case of not persistent inode , where doesn ' t
* matter if the inode is different or equal , because they have no
* meaning , and then we don ' t even save them
*/
2013-09-30 22:42:33 +02:00
+ + scan - > count_equal ;
2026-01-14 10:35:57 +01:00
if ( state - > opt . gui_verbose ) {
2026-05-29 11:01:44 +02:00
log_tag ( " scan:equal:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
2013-09-30 22:42:33 +02:00
}
}
2013-08-26 20:33:25 +02:00
2013-12-24 21:46:27 +01:00
/* mark the file as kept */
2014-02-12 20:02:35 +01:00
scan_file_keep ( scan , file ) ;
2013-12-24 21:46:27 +01:00
2013-08-26 20:33:25 +02:00
/* nothing more to do */
return ;
}
/* here if the file is changed but with the correct name */
2014-09-27 00:32:39 +02:00
2016-01-18 19:05:46 +01:00
/* save the info for later printout */
file_already_present_size = file - > size ;
file_already_present_mtime_sec = file - > mtime_sec ;
file_already_present_mtime_nsec = file - > mtime_nsec ;
2014-10-04 10:27:23 +02:00
/* keep track if the original file was not of zero size */
is_original_file_size_different_than_zero = file - > size ! = 0 ;
2011-06-20 20:35:04 +02:00
2026-08-17 23:57:56 +02:00
/* the old version doesn't represent the current inode anymore */
if ( file - > inode ! = INODE_INVALID ) {
tommy_hashdyn_remove_existing ( & disk - > inodeset , & file - > nodeset ) ;
file - > inode = INODE_INVALID ;
}
2026-07-16 23:22:15 +02:00
/* mark it as to be removed in Phase 2 */
file_flag_set ( file , FILE_IS_MODIFIED_OLD ) ;
2026-08-17 23:57:56 +02:00
/* flag the modified version to defer path/stamp insertion to Phase 3 */
2026-07-16 23:22:15 +02:00
is_file_modified = 1 ;
2013-08-26 20:33:25 +02:00
2014-12-09 21:25:17 +01:00
/* and continue to insert it again */
2016-01-18 22:39:39 +01:00
} else {
file_already_present_size = 0 ;
file_already_present_mtime_sec = 0 ;
file_already_present_mtime_nsec = 0 ;
2011-03-04 20:59:21 +01:00
}
2026-04-27 15:43:06 +02:00
/*
* Refresh the info , to ensure that they are synced ,
* note that we refresh only the info of the new or modified files
* because this is slow operation
*/
2014-10-14 22:47:21 +02:00
scan_file_refresh ( scan , sub , st , & physical ) ;
2014-10-04 10:27:23 +02:00
2015-11-14 17:03:26 +01:00
# ifndef _WIN32
2026-04-27 15:43:06 +02:00
/*
* Do a safety check to ensure that the common ext4 case of zeroing
* the size of a file after a crash doesn ' t propagate to the backup
* this check is specific for Linux , so we disable it on Windows
*/
2014-10-04 10:27:23 +02:00
if ( is_original_file_size_different_than_zero & & st - > st_size = = 0 ) {
if ( ! state - > opt . force_zero ) {
/* LCOV_EXCL_START */
2026-04-21 15:19:25 +02:00
log_error ( ESOFT , " The file '%s%s' has unexpected zero size! \n " , disk - > mount_point , sub ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " It's possible that after a kernel crash this file was lost, \n " ) ;
log_error ( ESOFT , " and you can use 'snapraid fix -f /%s' to recover it. \n " , fmt_poll ( disk , sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( ! is_diff ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " If this an expected condition you can '%s' anyway using 'snapraid --force-zero %s' \n " , state - > command , state - > command ) ;
2014-10-25 17:47:22 +02:00
exit ( EXIT_FAILURE ) ;
}
2014-10-04 10:27:23 +02:00
/* LCOV_EXCL_STOP */
}
}
2015-11-18 23:17:10 +01:00
# else
/* avoid the unused warning in Windows */
( void ) is_original_file_size_different_than_zero ;
2015-11-14 17:03:26 +01:00
# endif
2014-10-04 10:27:23 +02:00
2011-06-20 20:35:04 +02:00
/* insert it */
2013-06-25 22:41:10 +02:00
file = file_alloc ( state - > block_size , sub , st - > st_size , st - > st_mtime , STAT_NSEC ( st ) , st - > st_ino , physical ) ;
2011-03-20 22:36:49 +01:00
/* mark it as present */
2011-04-28 23:09:19 +02:00
file_flag_set ( file , FILE_IS_PRESENT ) ;
2011-03-04 20:59:21 +01:00
2026-04-27 15:43:06 +02:00
/*
* If copy detection is enabled
* note that the copy detection is tried also for updated files
* this makes sense because it may happen to have two different copies
* of the same file , and we move the right one over the wrong one
* in such case we have a " copy " over an " update "
*/
2014-05-15 23:45:51 +02:00
if ( ! state - > opt . force_nocopy ) {
2014-02-12 20:02:35 +01:00
tommy_uint32_t hash = file_stamp_hash ( file - > size , file - > mtime_sec , file - > mtime_nsec ) ;
2014-05-15 23:45:51 +02:00
/* search for a file with the same name and stamp in all the disks */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-05-15 23:45:51 +02:00
struct snapraid_disk * other_disk = i - > data ;
struct snapraid_file * other_file ;
2026-07-06 16:19:21 +02:00
char sub_other [ PATH_MAX ] ;
2021-11-28 15:50:38 +01:00
stamp_lock ( other_disk ) ;
2026-04-27 15:43:06 +02:00
/*
* If the nanosecond part of the time stamp is valid , search
* for name and stamp , otherwise for path and stamp
*/
2014-05-15 23:45:51 +02:00
if ( file - > mtime_nsec ! = 0 & & file - > mtime_nsec ! = STAT_NSEC_INVALID )
other_file = tommy_hashdyn_search ( & other_disk - > stampset , file_namestamp_compare , file , hash ) ;
else
other_file = tommy_hashdyn_search ( & other_disk - > stampset , file_pathstamp_compare , file , hash ) ;
2026-07-06 16:19:21 +02:00
if ( other_file ) {
/* copy sub path safely before unlocking stamp_lock */
pathcpy ( sub_other , sizeof ( sub_other ) , other_file - > sub ) ;
}
2021-11-28 15:50:38 +01:00
stamp_unlock ( other_disk ) ;
2014-05-15 23:45:51 +02:00
2026-07-16 23:22:15 +02:00
/*
* If found , check stability and copy the hash .
*
* This is safe to execute unlocked because :
* 1. Modified files deallocations are deferred to Phase 2 ( mono - threaded phase ) .
* 2. file_is_full_invalid_parity_and_stable ( ) only accepts files with complete
* invalid parity , preventing them from being selected by file_is_full_hashed_and_stable ( ) .
*/
2015-10-01 18:19:48 +02:00
if ( other_file & & file_is_full_hashed_and_stable ( scan - > state , other_disk , other_file ) ) {
2026-03-14 17:48:30 +01:00
char path_other [ PATH_MAX ] ;
struct stat other_st ;
stamp_lock ( other_disk ) ;
file_flag_set ( other_file , FILE_IS_RELOCATED ) ;
stamp_unlock ( other_disk ) ;
2014-05-15 23:45:51 +02:00
/* assume that the file is a copy, and reuse the hash */
file_copy ( other_file , file ) ;
2026-03-14 17:48:30 +01:00
/* check if other file still exists */
2026-07-06 16:19:21 +02:00
pathprint ( path_other , sizeof ( path_other ) , " %s%s " , other_disk - > dir , sub_other ) ;
2026-03-14 17:48:30 +01:00
if ( lstat ( path_other , & other_st ) = = 0 ) {
+ + scan - > count_copy ;
2014-05-15 23:45:51 +02:00
2026-07-06 16:19:21 +02:00
log_tag ( " scan:copy:%s:%s:%s:%s \n " , other_disk - > name , esc_tag ( sub_other ) , disk - > name , esc_tag ( file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
if ( is_diff ) {
2026-07-06 16:19:21 +02:00
msg_info ( " copy %s -> %s \n " , fmt_term ( other_disk , sub_other ) , fmt_term ( disk , file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
}
} else {
+ + scan - > count_relocate ;
2026-07-06 16:19:21 +02:00
log_tag ( " scan:relocate:%s:%s:%s:%s \n " , other_disk - > name , esc_tag ( sub_other ) , disk - > name , esc_tag ( file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
if ( is_diff ) {
2026-07-06 16:19:21 +02:00
msg_info ( " relocate %s -> %s \n " , fmt_term ( other_disk , sub_other ) , fmt_term ( disk , file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
}
2014-05-15 23:45:51 +02:00
}
2014-03-02 21:56:59 +01:00
2014-12-09 21:25:17 +01:00
/* mark it as reported */
is_file_reported = 1 ;
2014-05-15 23:45:51 +02:00
/* no need to continue the search */
break ;
2014-02-12 20:02:35 +01:00
}
}
}
2026-04-27 15:43:06 +02:00
/*
* If not yet reported , do it now
* we postpone this to avoid to print two times the copied files
*/
2014-12-09 21:25:17 +01:00
if ( ! is_file_reported ) {
if ( is_file_already_present ) {
+ + scan - > count_change ;
2026-05-29 11:01:44 +02:00
log_tag ( " scan:update:%s:%s: % " PRIu64 " % " PRIu64 " .%d -> % " PRIu64 " % " PRIu64 " .%d \n " , disk - > name , esc_tag ( sub ) ,
2017-10-27 22:54:42 +02:00
file_already_present_size , file_already_present_mtime_sec , file_already_present_mtime_nsec ,
file - > size , file - > mtime_sec , file - > mtime_nsec
2016-01-18 19:05:46 +01:00
) ;
2014-12-09 21:25:17 +01:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " update %s \n " , fmt_term ( disk , sub ) ) ;
2014-12-09 21:25:17 +01:00
}
} else {
+ + scan - > count_insert ;
2026-05-29 11:01:44 +02:00
log_tag ( " scan:add:%s:%s \n " , disk - > name , esc_tag ( sub ) ) ;
2014-12-09 21:25:17 +01:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " add %s \n " , fmt_term ( disk , sub ) ) ;
2014-12-09 21:25:17 +01:00
}
}
}
2014-03-02 21:56:59 +01:00
2026-08-17 23:57:56 +02:00
if ( ! is_file_modified ) {
scan_file_inode_insert ( scan , file ) ;
scan_file_stamp_insert ( scan , file ) ;
2026-07-16 23:22:15 +02:00
} else {
2026-08-17 23:57:56 +02:00
/*
* Insert the new inode before scanning the next path , so paths with the
* same inode are recognized as hardlinks to this file . Keep the old
* path / stamp entries until Phase 2 ; the new entries are inserted in Phase 3.
*/
scan_file_inode_insert ( scan , file ) ;
2026-07-16 23:22:15 +02:00
file_flag_set ( file , FILE_IS_MODIFIED_NEW ) ;
}
/* insert the file in the delayed allocation list */
scan_file_delayed_allocate ( scan , file ) ;
2011-08-26 14:18:38 +02:00
}
2013-01-27 16:44:04 +01:00
/**
2015-10-07 22:07:20 +02:00
* Remove the specified dir from the data set .
2013-01-27 16:44:04 +01:00
*/
2014-02-12 20:02:35 +01:00
static void scan_emptydir_remove ( struct snapraid_scan * scan , struct snapraid_dir * dir )
2013-01-27 16:44:04 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 16:44:04 +01:00
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-01-27 16:44:04 +01:00
/* remove the file from the dir containers */
tommy_hashdyn_remove_existing ( & disk - > dirset , & dir - > nodeset ) ;
tommy_list_remove_existing ( & disk - > dirlist , & dir - > nodelist ) ;
/* deallocate */
dir_free ( dir ) ;
}
/**
2015-10-07 22:07:20 +02:00
* Insert the specified dir in the data set .
2013-01-27 16:44:04 +01:00
*/
2014-02-12 20:02:35 +01:00
static void scan_emptydir_insert ( struct snapraid_scan * scan , struct snapraid_dir * dir )
2013-01-27 16:44:04 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 16:44:04 +01:00
/* state changed */
2021-11-28 15:50:38 +01:00
scan - > need_write = 1 ;
2013-01-27 16:44:04 +01:00
/* insert the dir in the dir containers */
tommy_hashdyn_insert ( & disk - > dirset , & dir - > nodeset , dir , dir_name_hash ( dir - > sub ) ) ;
tommy_list_insert_tail ( & disk - > dirlist , & dir - > nodelist , dir ) ;
}
/**
2015-10-07 22:07:20 +02:00
* Process a dir .
2013-01-27 16:44:04 +01:00
*/
2014-02-12 20:02:35 +01:00
static void scan_emptydir ( struct snapraid_scan * scan , const char * sub )
2013-01-27 16:44:04 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 16:44:04 +01:00
struct snapraid_dir * dir ;
/* check if the dir already exists */
dir = tommy_hashdyn_search ( & disk - > dirset , dir_name_compare , sub , dir_name_hash ( sub ) ) ;
if ( dir ) {
/* check if multiple files have the same name */
if ( dir_flag_has ( dir , FILE_IS_PRESENT ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency for dir '%s%s' \n " , disk - > dir , sub ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-27 16:44:04 +01:00
}
/* mark as present */
dir_flag_set ( dir , FILE_IS_PRESENT ) ;
/* nothing more to do */
return ;
} else {
/* and continue to insert it */
}
/* insert it */
dir = dir_alloc ( sub ) ;
/* mark it as present */
dir_flag_set ( dir , FILE_IS_PRESENT ) ;
/* insert it in the delayed insert list */
tommy_list_insert_tail ( & scan - > dir_insert_list , & dir - > nodelist , dir ) ;
}
2013-10-02 23:05:22 +02:00
struct dirent_sorted {
/* node for data structures */
tommy_node node ;
# if HAVE_STRUCT_DIRENT_D_INO
uint64_t d_ino ; /**< Inode number. */
# endif
# if HAVE_STRUCT_DIRENT_D_TYPE
uint32_t d_type ; /**< File type. */
# endif
# if HAVE_STRUCT_DIRENT_D_STAT
struct stat d_stat ; /**< Stat result. */
# endif
2014-03-10 21:59:25 +01:00
char d_name [ ] ; /**< Variable length name. It must be the last field. */
2013-10-02 23:05:22 +02:00
} ;
2013-10-03 21:45:48 +02:00
# if HAVE_STRUCT_DIRENT_D_INO
2013-10-02 23:05:22 +02:00
static int dd_ino_compare ( const void * void_a , const void * void_b )
{
const struct dirent_sorted * a = void_a ;
const struct dirent_sorted * b = void_b ;
if ( a - > d_ino < b - > d_ino )
return - 1 ;
if ( a - > d_ino > b - > d_ino )
return 1 ;
return 0 ;
}
# endif
2014-07-11 19:59:01 +02:00
static int dd_name_compare ( const void * void_a , const void * void_b )
{
const struct dirent_sorted * a = void_a ;
const struct dirent_sorted * b = void_b ;
return strcmp ( a - > d_name , b - > d_name ) ;
}
2013-10-02 23:05:22 +02:00
/**
2015-10-07 22:07:20 +02:00
* Return the stat info of a dir entry .
2013-10-02 23:05:22 +02:00
*/
# if HAVE_STRUCT_DIRENT_D_STAT
# define DSTAT(file, dd, buf) dstat(dd)
struct stat * dstat ( struct dirent_sorted * dd )
{
return & dd - > d_stat ;
}
# else
2026-05-03 11:25:24 +02:00
# define DSTAT(file, dd, buf) dstat(disk, file, buf)
struct stat * dstat ( struct snapraid_disk * disk , const char * file , struct stat * st )
2013-10-02 23:05:22 +02:00
{
if ( lstat ( file , st ) ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Stat error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( file ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in stat file/directory '%s'. %s. \n " , file , strerror ( errno ) ) ;
2013-10-02 23:05:22 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-10-02 23:05:22 +02:00
}
return st ;
}
# endif
2011-03-16 23:52:10 +01:00
/**
2015-10-07 22:07:20 +02:00
* Process a directory .
2013-01-27 16:44:04 +01:00
* Return ! = 0 if at least one file or link is processed .
2011-03-16 23:52:10 +01:00
*/
2022-01-09 10:23:09 +01:00
static int scan_sub ( struct snapraid_scan * scan , int level , int is_diff , char * path_next , char * sub_next , char * tmp )
2011-03-04 20:59:21 +01:00
{
2014-02-12 20:02:35 +01:00
struct snapraid_state * state = scan - > state ;
struct snapraid_disk * disk = scan - > disk ;
2013-01-27 16:44:04 +01:00
int processed = 0 ;
2011-03-04 20:59:21 +01:00
DIR * d ;
2013-10-02 23:05:22 +02:00
tommy_list list ;
tommy_node * node ;
2026-05-11 23:24:36 +02:00
size_t mount_point_len ;
2022-01-09 10:23:09 +01:00
size_t path_len ;
size_t sub_len ;
2026-05-11 23:24:36 +02:00
mount_point_len = strlen ( disk - > mount_point ) ;
2022-01-09 10:23:09 +01:00
path_len = strlen ( path_next ) ;
sub_len = strlen ( sub_next ) ;
2013-10-02 23:05:22 +02:00
tommy_list_init ( & list ) ;
2011-03-04 20:59:21 +01:00
2022-01-09 10:23:09 +01:00
d = opendir ( path_next ) ;
2011-03-04 20:59:21 +01:00
if ( ! d ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Open dir error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening directory '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2015-06-23 21:39:18 +02:00
if ( level = = 0 )
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " If this is the disk mount point, remember to create it manually \n " ) ;
2015-06-23 21:39:18 +02:00
else
2026-05-03 11:25:24 +02:00
log_fatal ( errno , " If it's a permission problem, you can exclude it in the config file with: exclude /%s \n " , sub_next ) ;
2011-03-04 20:59:21 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-04 20:59:21 +01:00
}
2013-10-02 23:05:22 +02:00
/* read the full directory */
while ( 1 ) {
struct dirent_sorted * entry ;
2011-05-04 19:28:36 +02:00
const char * name ;
struct dirent * dd ;
2013-10-02 23:05:22 +02:00
size_t name_len ;
2011-05-04 19:28:36 +02:00
2016-09-23 16:46:54 +02:00
/*
* Clear errno to differentiate the end of the stream and an error condition
*
* From the Linux readdir ( ) manpage :
* " If the end of the directory stream is reached, NULL is returned and errno is not changed.
* If an error occurs , NULL is returned and errno is set appropriately . "
*/
2011-05-04 19:28:36 +02:00
errno = 0 ;
dd = readdir ( d ) ;
if ( dd = = 0 & & errno ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2022-01-09 10:23:09 +01:00
/* restore removing additions */
path_next [ path_len ] = 0 ;
sub_next [ sub_len ] = 0 ;
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Read dir error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reading directory '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2026-05-03 11:25:24 +02:00
log_fatal ( errno , " You can exclude it in the config file with: exclude /%s \n " , sub_next ) ;
2011-05-04 19:28:36 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-05-04 19:28:36 +02:00
}
2013-02-27 21:07:16 +01:00
if ( dd = = 0 ) {
2011-05-04 19:28:36 +02:00
break ; /* finished */
}
2011-03-20 22:36:49 +01:00
/* skip "." and ".." files */
2011-05-04 19:28:36 +02:00
name = dd - > d_name ;
2011-03-22 23:23:45 +01:00
if ( name [ 0 ] = = ' . ' & & ( name [ 1 ] = = 0 | | ( name [ 1 ] = = ' . ' & & name [ 2 ] = = 0 ) ) )
2011-03-20 22:36:49 +01:00
continue ;
2011-03-12 10:09:59 +01:00
2022-01-09 10:23:09 +01:00
pathcatl ( path_next , path_len , PATH_MAX , name ) ;
2011-06-26 21:37:47 +02:00
2013-10-01 18:22:41 +02:00
/* check for not supported file names */
if ( name [ 0 ] = = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Unsupported name error. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( path_next ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Unsupported name '%s' in file '%s'. \n " , name , path_next ) ;
2011-05-04 19:28:36 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-05-04 19:28:36 +02:00
}
2026-08-26 12:53:43 +02:00
/*
* Exclude hidden files even before calling lstat ( ) .
* Note that . snapraidignore is an exception because it is a control file
* that defines local exclusion rules , and must also be preserved as array
* data so that exclusion rules survive disaster recovery .
*/
2013-01-18 00:15:55 +01:00
if ( filter_hidden ( state - > filter_hidden , dd ) ! = 0 ) {
2026-08-26 12:53:43 +02:00
if ( pathcmp ( " .snapraidignore " , dd - > d_name ) ! = 0 ) {
msg_verbose ( " Excluding hidden '%s' \n " , path_next ) ;
continue ;
}
2012-11-04 00:46:16 +01:00
}
2013-05-14 21:34:51 +02:00
/* exclude content files even before calling lstat() */
2026-05-11 23:24:36 +02:00
if ( filter_content ( & state - > contentlist , disk - > mount_point , mount_point_len , sub_next , sub_len , name ) ! = 0 ) {
2015-02-22 14:31:03 +01:00
msg_verbose ( " Excluding content '%s' \n " , path_next ) ;
2013-01-18 00:15:55 +01:00
continue ;
}
2026-05-11 23:24:36 +02:00
/* exclude snapshot container even before calling lstat() */
2026-08-14 09:47:12 +02:00
if ( filter_snapshot ( state - > snapshot , sub_next , name ) ! = 0 ) {
2026-05-11 23:24:36 +02:00
msg_verbose ( " Excluding snapshots directory '%s' \n " , path_next ) ;
continue ;
}
2013-10-02 23:05:22 +02:00
name_len = strlen ( dd - > d_name ) ;
2014-03-10 21:59:25 +01:00
entry = malloc_nofail ( sizeof ( struct dirent_sorted ) + name_len + 1 ) ;
2013-10-02 23:05:22 +02:00
/* copy the dir entry */
# if HAVE_STRUCT_DIRENT_D_INO
entry - > d_ino = dd - > d_ino ;
# endif
# if HAVE_STRUCT_DIRENT_D_TYPE
entry - > d_type = dd - > d_type ;
# endif
# if HAVE_STRUCT_DIRENT_D_STAT
2013-01-18 00:15:55 +01:00
/* convert dirent to lstat result */
2013-10-02 23:05:22 +02:00
dirent_lstat ( dd , & entry - > d_stat ) ;
2014-10-01 21:44:35 +02:00
/* note that at this point the st_mode may be 0 */
2013-10-02 23:05:22 +02:00
# endif
memcpy ( entry - > d_name , dd - > d_name , name_len + 1 ) ;
/* insert in the list */
tommy_list_insert_tail ( & list , & entry - > node , entry ) ;
2026-01-10 16:30:35 +01:00
/* process ignore files */
2026-08-26 12:53:43 +02:00
if ( pathcmp ( " .snapraidignore " , dd - > d_name ) = = 0 )
2026-01-10 16:30:35 +01:00
state_load_ignore_file ( & scan - > local_filter_list , path_next , sub_next ) ;
2013-10-02 23:05:22 +02:00
}
if ( closedir ( d ) ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2022-01-09 10:23:09 +01:00
/* restore removing additions */
path_next [ path_len ] = 0 ;
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Close dir error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error closing directory '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2013-10-02 23:05:22 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-10-02 23:05:22 +02:00
}
2014-07-11 19:59:01 +02:00
if ( state - > opt . force_order = = SORT_ALPHA ) {
2026-04-27 15:43:06 +02:00
/*
* If requested sort alphabetically
* this is mainly done for testing to ensure to always
* process in the same way in different platforms
*/
2014-07-11 19:59:01 +02:00
tommy_list_sort ( & list , dd_name_compare ) ;
2014-07-11 23:33:19 +02:00
}
# if HAVE_STRUCT_DIRENT_D_INO
else if ( ! disk - > has_volatile_inodes ) {
2026-04-27 15:43:06 +02:00
/*
* If inodes are persistent
* sort the list of dir entries by inodes
*/
2013-10-02 23:05:22 +02:00
tommy_list_sort ( & list , dd_ino_compare ) ;
}
2014-07-11 19:59:01 +02:00
/* otherwise just keep the insertion order */
2013-10-02 23:05:22 +02:00
# endif
/* process the sorted dir entries */
node = list ;
2014-09-27 00:32:39 +02:00
while ( node ! = 0 ) {
2015-01-27 23:10:38 +01:00
struct snapraid_filter * reason = 0 ;
2013-10-02 23:05:22 +02:00
struct dirent_sorted * dd = node - > data ;
const char * name = dd - > d_name ;
struct stat * st ;
int type ;
# if !HAVE_STRUCT_DIRENT_D_STAT
struct stat st_buf ;
# endif
2022-01-09 10:23:09 +01:00
pathcatl ( path_next , path_len , PATH_MAX , name ) ;
pathcatl ( sub_next , sub_len , PATH_MAX , name ) ;
2013-10-02 23:05:22 +02:00
/* start with an unknown type */
type = - 1 ;
st = 0 ;
/* if dirent has the type, use it */
# if HAVE_STRUCT_DIRENT_D_TYPE
switch ( dd - > d_type ) {
case DT_UNKNOWN : break ;
case DT_REG : type = 0 ; break ;
case DT_LNK : type = 1 ; break ;
case DT_DIR : type = 2 ; break ;
default : type = 3 ; break ;
2011-03-22 23:23:45 +01:00
}
2013-01-18 00:15:55 +01:00
# endif
2011-03-22 23:23:45 +01:00
2013-10-02 23:05:22 +02:00
/* if type is still unknown */
if ( type < 0 ) {
/* get the type from stat */
st = DSTAT ( path_next , dd , & st_buf ) ;
2014-10-01 21:44:35 +02:00
# if HAVE_STRUCT_DIRENT_D_STAT
2026-04-27 15:43:06 +02:00
/*
* If the st_mode field is missing , takes care to fill it using normal lstat ( )
* at now this can happen only in Windows ( with HAVE_STRUCT_DIRENT_D_STAT defined ) ,
* because we use a directory reading method that doesn ' t read info about ReparsePoint .
* Note that here we cannot call here lstat_sync ( ) , because we don ' t know what kind
* of file is it , and lstat_sync ( ) doesn ' t always work
*/
2014-11-12 20:47:07 +01:00
if ( st - > st_mode = = 0 ) {
2014-10-01 21:44:35 +02:00
if ( lstat ( path_next , st ) ! = 0 ) {
2014-09-30 21:54:49 +02:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Stat error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in stat file/directory '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2014-09-30 21:54:49 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
# endif
2013-10-02 23:05:22 +02:00
if ( S_ISREG ( st - > st_mode ) )
type = 0 ;
else if ( S_ISLNK ( st - > st_mode ) )
type = 1 ;
else if ( S_ISDIR ( st - > st_mode ) )
type = 2 ;
else
type = 3 ;
}
if ( type = = 0 ) { /* REG */
2026-08-26 12:53:43 +02:00
/*
* Note that . snapraidignore is an exception from filtering because it
* is a control file that must be preserved as array data .
*/
if ( pathcmp ( " .snapraidignore " , name ) = = 0
| | ( filter_path ( & state - > filterlist , & reason , disk - > name , sub_next ) = = 0
& & filter_path ( & scan - > local_filter_list , & reason , disk - > name , sub_next ) = = 0 ) ) {
2013-06-25 22:41:10 +02:00
2014-09-30 21:54:49 +02:00
/* late stat, if not yet called */
if ( ! st )
st = DSTAT ( path_next , dd , & st_buf ) ;
2013-10-02 23:05:22 +02:00
2014-10-04 10:27:23 +02:00
# if HAVE_LSTAT_SYNC
2026-04-27 15:43:06 +02:00
/*
* If the st_ino field is missing , takes care to fill it using the extended lstat ( )
* this can happen only in Windows
*/
2026-07-30 16:20:26 +02:00
if ( st - > st_ino = = INODE_INVALID | | st - > st_nlink = = 0 ) {
2014-10-14 22:47:21 +02:00
if ( lstat_sync ( path_next , st , 0 ) ! = 0 ) {
2014-09-30 21:54:49 +02:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Stat error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in stat file '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2014-09-30 21:54:49 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2011-05-04 19:28:36 +02:00
}
# endif
2014-09-30 21:54:49 +02:00
2014-10-25 17:47:22 +02:00
scan_file ( scan , is_diff , sub_next , st , FILEPHY_UNREAD_OFFSET ) ;
2013-01-27 16:44:04 +01:00
processed = 1 ;
2011-03-16 23:52:10 +01:00
} else {
2022-01-09 10:23:09 +01:00
msg_verbose ( " Excluding file '%s' for rule '%s' \n " , path_next , filter_type ( reason , tmp , PATH_MAX ) ) ;
2011-03-16 23:52:10 +01:00
}
2013-10-02 23:05:22 +02:00
} else if ( type = = 1 ) { /* LNK */
2026-08-26 12:53:43 +02:00
/*
* Note that . snapraidignore is an exception from filtering because it
* is a control file that must be preserved as array data .
*/
if ( pathcmp ( " .snapraidignore " , name ) = = 0
| | ( filter_path ( & state - > filterlist , & reason , disk - > name , sub_next ) = = 0
& & filter_path ( & scan - > local_filter_list , & reason , disk - > name , sub_next ) = = 0 ) ) {
2026-06-04 00:34:51 +02:00
ssize_t ret ;
2011-08-26 14:18:38 +02:00
2022-01-09 10:23:09 +01:00
ret = readlink ( path_next , tmp , PATH_MAX ) ;
2011-08-26 14:18:38 +02:00
if ( ret > = PATH_MAX ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Readlink error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Error in readlink file '%s'. Symlink too long. \n " , path_next ) ;
2011-08-26 14:18:38 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-26 14:18:38 +02:00
}
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Readlink error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error in readlink file '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
2011-08-26 14:18:38 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-08-26 14:18:38 +02:00
}
2015-12-18 19:41:49 +01:00
if ( ret = = 0 )
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Empty symbolic link '%s'. \n " , path_next ) ;
2011-08-26 14:18:38 +02:00
/* readlink doesn't put the final 0 */
2022-01-09 08:46:06 +01:00
tmp [ ret ] = 0 ;
2011-08-26 14:18:38 +02:00
2013-01-27 18:06:15 +01:00
/* process as a symbolic link */
2022-01-09 08:46:06 +01:00
scan_link ( scan , is_diff , sub_next , tmp , FILE_IS_SYMLINK ) ;
2013-01-27 16:44:04 +01:00
processed = 1 ;
2011-08-26 14:18:38 +02:00
} else {
2022-01-09 10:23:09 +01:00
msg_verbose ( " Excluding link '%s' for rule '%s' \n " , path_next , filter_type ( reason , tmp , PATH_MAX ) ) ;
2011-08-26 14:18:38 +02:00
}
2013-10-02 23:05:22 +02:00
} else if ( type = = 2 ) { /* DIR */
2026-01-10 16:30:35 +01:00
if ( filter_subdir ( & state - > filterlist , & reason , disk - > name , sub_next ) = = 0
& & filter_subdir ( & scan - > local_filter_list , & reason , disk - > name , sub_next ) = = 0 ) {
2013-07-24 21:56:52 +02:00
# ifndef _WIN32
2014-10-01 21:44:35 +02:00
/* late stat, if not yet called */
if ( ! st )
st = DSTAT ( path_next , dd , & st_buf ) ;
2013-10-02 23:05:22 +02:00
2026-08-26 08:36:19 +02:00
struct fsidentity identity ;
if ( fsidentity ( path_next , st , & identity ) ! = 0 ) {
/* LCOV_EXCL_START */
/* restore removing additions */
path_next [ path_len ] = 0 ;
sub_next [ sub_len ] = 0 ;
log_tag ( " %s:%u:%s:%s: Statx error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( path_next ) , strerror ( errno ) ) ;
log_fatal ( errno , " Error accessing directory '%s'. %s. \n " , path_next , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-05-03 11:25:24 +02:00
/*
2026-08-26 08:36:19 +02:00
* In Unix don ' t follow mount points in different devices or subvolumes
2026-05-03 11:25:24 +02:00
* in Windows we are already skipping them reporting them as special files
*/
2026-08-26 08:36:19 +02:00
if ( identity . device ! = scan - > dir_identity . device ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Ignoring mount point. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( path_next ) ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Ignoring mount point '%s' because it appears to be in a different device \n " , path_next ) ;
2026-08-26 08:36:19 +02:00
} else if ( identity . mnt_id ! = scan - > dir_identity . mnt_id ) {
log_tag ( " %s:%u:%s:%s: Ignoring mount point. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( path_next ) ) ;
log_error ( ESOFT , " WARNING! Ignoring mount point '%s' because it belongs to a different mount \n " , path_next ) ;
} else if ( identity . subvol ! = scan - > dir_identity . subvol ) {
log_tag ( " %s:%u:%s:%s: Ignoring nested subvolume. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( path_next ) ) ;
log_error ( ESOFT , " WARNING! Ignoring nested subvolume '%s' because it belongs to a different subvolume \n " , path_next ) ;
2013-07-24 21:56:52 +02:00
} else
# endif
{
2013-06-21 23:57:58 +02:00
/* recurse */
2022-01-09 10:23:09 +01:00
pathslash ( path_next , PATH_MAX ) ;
pathslash ( sub_next , PATH_MAX ) ;
if ( scan_sub ( scan , level + 1 , is_diff , path_next , sub_next , tmp ) = = 0 ) {
/* restore removing additions */
pathcatl ( sub_next , sub_len , PATH_MAX , name ) ;
2013-06-21 23:57:58 +02:00
/* scan the directory as empty dir */
2014-02-12 20:02:35 +01:00
scan_emptydir ( scan , sub_next ) ;
2013-06-21 23:57:58 +02:00
}
/* or we processed something internally, or we have added the empty dir */
processed = 1 ;
2013-01-27 16:44:04 +01:00
}
2011-03-20 22:36:49 +01:00
} else {
2022-01-09 10:23:09 +01:00
msg_verbose ( " Excluding directory '%s' for rule '%s' \n " , path_next , filter_type ( reason , tmp , PATH_MAX ) ) ;
2011-03-04 20:59:21 +01:00
}
} else {
2026-01-10 16:30:35 +01:00
if ( filter_path ( & state - > filterlist , & reason , disk - > name , sub_next ) = = 0
& & filter_path ( & scan - > local_filter_list , & reason , disk - > name , sub_next ) = = 0 ) {
2014-10-01 21:44:35 +02:00
/* late stat, if not yet called */
if ( ! st )
st = DSTAT ( path_next , dd , & st_buf ) ;
2014-09-27 00:32:39 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Ignoring special file. \n " , es ( ESOFT ) , 0 , disk - > name , esc_tag ( path_next ) ) ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Ignoring special '%s' file '%s' \n " , stat_desc ( st ) , path_next ) ;
2011-06-11 09:38:19 +02:00
} else {
2022-01-09 10:23:09 +01:00
msg_verbose ( " Excluding special file '%s' for rule '%s' \n " , path_next , filter_type ( reason , tmp , PATH_MAX ) ) ;
2011-03-04 20:59:21 +01:00
}
}
2013-10-02 23:05:22 +02:00
/* next entry */
node = node - > next ;
/* free the present one */
free ( dd ) ;
2011-05-04 19:28:36 +02:00
}
2013-01-27 16:44:04 +01:00
return processed ;
2011-03-04 20:59:21 +01:00
}
2022-01-09 10:23:09 +01:00
/**
* Process a directory .
* Return ! = 0 if at least one file or link is processed .
*/
static int scan_dir ( struct snapraid_scan * scan , int level , int is_diff , const char * dir , const char * sub )
{
/* working buffers used by scan_sub() */
char path_next [ PATH_MAX ] ;
char sub_next [ PATH_MAX ] ;
char tmp [ PATH_MAX ] ;
pathcpy ( path_next , sizeof ( path_next ) , dir ) ;
pathcpy ( sub_next , sizeof ( sub_next ) , sub ) ;
return scan_sub ( scan , level , is_diff , path_next , sub_next , tmp ) ;
}
2021-11-28 15:50:38 +01:00
static void * scan_disk ( void * arg )
{
struct snapraid_scan * scan = arg ;
struct snapraid_disk * disk = scan - > disk ;
int ret ;
int has_persistent_inodes ;
int has_syncronized_hardlinks ;
2021-11-29 16:50:30 +01:00
uint64_t start ;
2021-11-28 15:50:38 +01:00
/* check if the disk supports persistent inodes */
2026-01-29 18:43:00 +01:00
ret = fsinfo ( disk - > dir , & has_persistent_inodes , & has_syncronized_hardlinks , 0 , 0 , 0 , 0 , 0 , 0 ) ;
2021-11-28 15:50:38 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%u:%s:%s: Filesystem info error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( disk - > dir ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing disk '%s' to get file-system info. %s. \n " , disk - > dir , strerror ( errno ) ) ;
2021-11-28 15:50:38 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! has_persistent_inodes ) {
disk - > has_volatile_inodes = 1 ;
}
if ( ! has_syncronized_hardlinks ) {
disk - > has_volatile_hardlinks = 1 ;
}
/* if inodes or UUID are not persistent/changed/unsupported */
if ( disk - > has_volatile_inodes | | disk - > has_different_uuid | | disk - > has_unsupported_uuid ) {
2026-04-27 15:43:06 +02:00
/*
* Remove all the inodes from the inode collection
* if they are not persistent , all of them could be changed now
* and we don ' t want to find false matching ones
* See scan_file ( ) for more details
*/
2021-11-28 15:50:38 +01:00
tommy_node * node = disk - > filelist ;
while ( node ) {
struct snapraid_file * file = node - > data ;
node = node - > next ;
/* remove from the inode set */
2026-07-30 16:20:26 +02:00
if ( file - > inode ! = INODE_INVALID )
tommy_hashdyn_remove_existing ( & disk - > inodeset , & file - > nodeset ) ;
2021-11-28 15:50:38 +01:00
/* clear the inode */
2026-07-30 16:20:26 +02:00
file - > inode = INODE_INVALID ;
2021-11-28 15:50:38 +01:00
}
}
2026-03-02 01:13:19 +01:00
start = os_tick_ms ( ) ;
2021-11-29 16:50:30 +01:00
2026-08-26 08:36:19 +02:00
struct stat st ;
if ( lstat ( disk - > dir , & st ) ! = 0 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:%u:%s:%s: Stat error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( disk - > dir ) , strerror ( errno ) ) ;
log_fatal ( errno , " Error accessing directory '%s'. %s. \n " , disk - > dir , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( fsidentity ( disk - > dir , & st , & scan - > dir_identity ) ! = 0 ) {
/* LCOV_EXCL_START */
log_tag ( " %s:%u:%s:%s: Statx error. %s. \n " , es ( errno ) , 0 , disk - > name , esc_tag ( disk - > dir ) , strerror ( errno ) ) ;
log_fatal ( errno , " Error accessing directory '%s'. %s. \n " , disk - > dir , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2021-11-28 15:50:38 +01:00
scan_dir ( scan , 0 , scan - > is_diff , disk - > dir , " " ) ;
2021-11-29 16:50:30 +01:00
if ( ! scan - > is_diff )
2026-03-02 01:13:19 +01:00
msg_progress ( " Scanned %s in % " PRIu64 " seconds \n " , disk - > name , ( os_tick_ms ( ) - start ) / 1000 ) ;
2021-11-29 16:50:30 +01:00
2021-11-28 15:50:38 +01:00
return 0 ;
}
2015-03-02 23:01:15 +01:00
static int state_diffscan ( struct snapraid_state * state , int is_diff )
2011-03-04 20:59:21 +01:00
{
2011-09-23 21:48:50 +02:00
tommy_node * i ;
tommy_node * j ;
tommy_list scanlist ;
2014-04-03 23:02:43 +02:00
int done ;
2026-02-01 20:21:28 +01:00
msg_ptr * msg ;
2015-02-22 14:31:03 +01:00
struct snapraid_scan total ;
int no_difference ;
2011-09-23 21:48:50 +02:00
tommy_list_init ( & scanlist ) ;
2011-03-04 20:59:21 +01:00
2014-10-25 17:47:22 +02:00
if ( is_diff )
2015-02-22 14:31:03 +01:00
msg_progress ( " Comparing... \n " ) ;
2021-11-28 15:50:38 +01:00
else
msg_progress ( " Scanning... \n " ) ;
2013-10-19 13:24:35 +02:00
2026-02-09 15:50:58 +01:00
log_tag ( " list:scan_begin \n " ) ;
2022-01-09 08:46:06 +01:00
/* allocate all the scan data */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2011-09-23 21:48:50 +02:00
struct snapraid_disk * disk = i - > data ;
struct snapraid_scan * scan ;
2013-09-25 23:20:07 +02:00
2021-11-28 15:50:38 +01:00
scan = scan_alloc ( state , disk , is_diff ) ;
2013-09-25 23:20:07 +02:00
2021-11-28 15:50:38 +01:00
tommy_list_insert_tail ( & scanlist , & scan - > node , scan ) ;
}
2013-09-25 23:20:07 +02:00
2026-07-16 23:22:15 +02:00
/*
* We split the search in three phases :
* Phase 1 : Parallel scanning of directories , finding new and modified files ( without deletions / deallocations ) .
* Phase 2 : Serialized removals ( deleted files and old versions of modified files ) to free up parity space .
* Phase 3 : Serialized insertions and allocations of new files .
*
* We must start Phase 2 ( deletions ) only when all disks have finished Phase 1 ( scanning ) ,
* to ensure that copy / relocation detection on any disk can search the stampset of other disks
* before their old files are deleted / deallocated .
*/
/*
* Phase 1 : Parallel scanning of directories
2026-08-18 00:06:15 +02:00
*
* Invariants during and after this phase :
* - Each disk is modified only by its scan thread . Cross - disk stampset
* accesses used by copy / relocate detection are protected by stamp_lock ( ) .
* - No old file , link , directory , or parity allocation is removed . This keeps
* every old stamp available to all scan threads for copy / relocate detection .
* - For every valid inode already encountered in the current scan , inodeset
* points to the current FILE_IS_PRESENT file . Later paths with the same inode
* are therefore represented as hardlinks to that single file .
* - Files with INODE_INVALID are never inserted into inodeset . Inode validity
* does not change when their path / stamp insertion is scheduled .
* - A new normal file is immediately inserted into all applicable
* inode / path / stamp sets and queued for delayed parity allocation .
* - For a modified file , FILE_IS_MODIFIED_OLD has INODE_INVALID and is no
* longer in inodeset , but remains in pathset / stampset and filelist .
* FILE_IS_MODIFIED_NEW is in inodeset only if its inode is valid , has no
* path / stamp entry , and is queued for Phase 3.
* - For a reallocated unchanged file , FILE_IS_REALLOC_OLD remains in all its
* original containers . FILE_IS_REALLOC_NEW is only queued for Phase 3 and
* is not inserted into any file container yet .
2026-07-16 23:22:15 +02:00
*/
2021-11-28 15:50:38 +01:00
for ( i = scanlist ; i ! = 0 ; i = i - > next ) {
struct snapraid_scan * scan = i - > data ;
# if HAVE_THREAD
2022-01-07 21:03:51 +01:00
if ( state - > opt . skip_multi_scan )
scan_disk ( scan ) ;
else
thread_create ( & scan - > thread , scan_disk , scan ) ;
2021-11-28 15:50:38 +01:00
# else
scan_disk ( scan ) ;
# endif
}
2013-09-25 23:20:07 +02:00
2021-11-28 15:50:38 +01:00
# if HAVE_THREAD
/* wait for all threads to terminate */
for ( i = scanlist ; i ! = 0 ; i = i - > next ) {
struct snapraid_scan * scan = i - > data ;
void * retval ;
2013-08-27 23:02:12 +02:00
2021-11-28 15:50:38 +01:00
/* wait for thread termination */
2022-01-07 21:03:51 +01:00
if ( ! state - > opt . skip_multi_scan )
thread_join ( scan - > thread , & retval ) ;
2014-02-12 20:02:35 +01:00
}
2021-11-28 15:50:38 +01:00
# endif
2014-02-12 20:02:35 +01:00
2014-09-27 00:32:39 +02:00
for ( i = scanlist ; i ! = 0 ; i = i - > next ) {
2014-02-12 20:02:35 +01:00
struct snapraid_scan * scan = i - > data ;
struct snapraid_disk * disk = scan - > disk ;
tommy_node * node ;
unsigned phy_dup ;
uint64_t phy_last ;
struct snapraid_file * phy_file_last ;
2011-03-04 20:59:21 +01:00
2026-07-16 23:22:15 +02:00
/*
* Phase 2 : Removals ( deleted files and old versions of modified files )
2026-08-18 00:06:15 +02:00
*
* Invariants on entry and during this phase :
* - All Phase 1 scan threads have terminated , so removals are serialized and
* no copy / relocate detection can still reference an old stamp or allocation .
* - Existing files not marked FILE_IS_PRESENT are still in their applicable
* inode / path / stamp sets and filelist , ready for normal removal .
* - FILE_IS_MODIFIED_OLD is no longer in inodeset and has INODE_INVALID , so
* scan_file_remove ( ) removes only its old path / stamp entries and allocation .
* A replacement with a valid inode remains in inodeset during the removal .
* - FILE_IS_REALLOC_OLD is still in all applicable inode / path / stamp sets and
* filelist , while FILE_IS_REALLOC_NEW is in no file container .
* - New normal files are already in all applicable inode / path / stamp sets ,
* modified new files have only their applicable inode entry , and all new
* versions are also in file_insert_list , without parity allocation or
* filelist membership .
* - Every old file / link / directory node is removed at most once . Deallocation
* happens before any Phase 3 allocation , making the freed parity reusable .
* - On exit , no old path / stamp entry conflicts with a queued modified version ;
* a queued reallocation has no old inode / path / stamp entry left either .
2026-07-16 23:22:15 +02:00
*/
2011-03-04 20:59:21 +01:00
/* check for removed files */
node = disk - > filelist ;
while ( node ) {
struct snapraid_file * file = node - > data ;
/* next node */
node = node - > next ;
2026-07-16 23:22:15 +02:00
if ( file_flag_has ( file , FILE_IS_REALLOC_OLD ) ) {
scan_file_remove ( scan , file , 0 ) ;
} else if ( file_flag_has ( file , FILE_IS_MODIFIED_OLD ) ) {
scan_file_remove ( scan , file , 1 ) ;
} else if ( ! file_flag_has ( file , FILE_IS_PRESENT ) ) {
2026-05-21 10:28:46 +02:00
/* here we are in mono-thread context, no need to use the stamp_lock() to read FILE_IS_RELOCATED */
2026-03-14 17:48:30 +01:00
if ( ! file_flag_has ( file , FILE_IS_RELOCATED ) ) {
+ + scan - > count_remove ;
2011-06-20 20:35:04 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " scan:remove:%s:%s \n " , disk - > name , esc_tag ( file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " remove %s \n " , fmt_term ( disk , file - > sub ) ) ;
2026-03-14 17:48:30 +01:00
}
2011-06-20 20:35:04 +02:00
}
2026-04-27 14:39:38 +02:00
scan_file_remove ( scan , file , 1 ) ;
2011-03-04 20:59:21 +01:00
}
}
2011-08-26 14:18:38 +02:00
/* check for removed links */
node = disk - > linklist ;
while ( node ) {
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink = node - > data ;
2011-08-26 14:18:38 +02:00
/* next node */
node = node - > next ;
/* remove if not present */
2015-10-08 22:19:52 +02:00
if ( ! link_flag_has ( slink , FILE_IS_PRESENT ) ) {
2011-09-23 21:48:50 +02:00
+ + scan - > count_remove ;
2011-08-26 14:18:38 +02:00
2026-05-29 11:01:44 +02:00
log_tag ( " scan:remove:%s:%s \n " , disk - > name , esc_tag ( slink - > sub ) ) ;
2014-10-25 17:47:22 +02:00
if ( is_diff ) {
2026-05-29 11:01:44 +02:00
msg_info ( " remove %s \n " , fmt_term ( disk , slink - > sub ) ) ;
2011-08-26 14:18:38 +02:00
}
2015-10-08 22:19:52 +02:00
scan_link_remove ( scan , slink ) ;
2011-08-26 14:18:38 +02:00
}
}
2013-01-27 16:44:04 +01:00
/* check for removed dirs */
node = disk - > dirlist ;
while ( node ) {
struct snapraid_dir * dir = node - > data ;
/* next node */
node = node - > next ;
/* remove if not present */
if ( ! dir_flag_has ( dir , FILE_IS_PRESENT ) ) {
2014-02-12 20:02:35 +01:00
scan_emptydir_remove ( scan , dir ) ;
2013-01-27 16:44:04 +01:00
}
}
2026-04-27 15:43:06 +02:00
/*
2026-07-16 23:22:15 +02:00
* Phase 3 : Insertions ( new files and new versions of modified files )
*
2026-08-18 00:06:15 +02:00
* Invariants on entry and after each insertion :
* - All removals and deallocations for this disk are complete , and every file
* to allocate is present exactly once in file_insert_list .
* - A new normal file is already in all applicable inode / path / stamp sets , so
* it requires no further container insertion .
* - A FILE_IS_MODIFIED_NEW with a valid inode is already in inodeset from
* Phase 1 for hardlink detection ; only its path / stamp nodes are inserted here .
* - FILE_IS_REALLOC_NEW is in no file container ; all its applicable
* inode / path / stamp nodes are inserted here after FILE_IS_REALLOC_OLD was
* removed in Phase 2.
* - The flags select the insertion path directly . No lookup or idempotent
* insertion is used , and every TommyDS node is inserted exactly once .
* - Container insertion precedes scan_file_allocate ( ) , which adds the parity
* allocation and filelist node . Links and directories are inserted after files .
* - On exit , every current file is in pathset / stampset , every valid inode has
* one file in inodeset , and all other paths for that inode are hardlinks .
* state_fscheck ( ) validates the resulting structures after all disks finish .
*
2026-04-27 15:43:06 +02:00
* Sort the files before inserting them
* we use a stable sort to ensure that if the reported physical offset / inode
* are always 0 , we keep at least the directory order
*/
2013-06-29 18:11:36 +02:00
switch ( state - > opt . force_order ) {
2013-06-25 22:41:10 +02:00
case SORT_PHYSICAL :
tommy_list_sort ( & scan - > file_insert_list , file_physical_compare ) ;
break ;
case SORT_INODE :
tommy_list_sort ( & scan - > file_insert_list , file_inode_compare ) ;
break ;
case SORT_ALPHA :
2014-02-12 20:02:35 +01:00
tommy_list_sort ( & scan - > file_insert_list , file_path_compare ) ;
2013-06-25 22:41:10 +02:00
break ;
case SORT_DIR :
/* already in order */
break ;
}
2026-04-27 15:43:06 +02:00
/*
* Insert all the new files , we insert them only after the deletion
* to reuse the just freed space
* also check if the physical offset reported are fakes or not
*/
2011-09-23 21:48:50 +02:00
node = scan - > file_insert_list ;
2013-09-25 23:20:07 +02:00
phy_dup = 0 ;
2014-10-03 21:28:56 +02:00
phy_last = FILEPHY_UNREAD_OFFSET ;
2013-12-02 19:03:00 +01:00
phy_file_last = 0 ;
2011-05-24 21:49:15 +02:00
while ( node ) {
struct snapraid_file * file = node - > data ;
2013-09-25 23:20:07 +02:00
/* if the file is not empty, count duplicate physical offsets */
if ( state - > opt . force_order = = SORT_PHYSICAL & & file - > size ! = 0 ) {
2014-10-03 21:28:56 +02:00
if ( phy_file_last ! = 0 & & file - > physical = = phy_last
2014-11-12 20:47:07 +01:00
/* files without offset are expected to have duplicates */
2014-10-03 21:28:56 +02:00
& & phy_last ! = FILEPHY_WITHOUT_OFFSET
) {
2026-04-27 15:43:06 +02:00
/*
* If verbose , print the list of duplicates real offsets
* other cases are for offsets not supported , so we don ' t need to report them file by file
*/
2015-02-22 14:31:03 +01:00
if ( phy_last > = FILEPHY_REAL_OFFSET ) {
2026-04-21 15:19:25 +02:00
log_info ( ESOFT , " WARNING! Files '%s%s' and '%s%s' share the same physical offset % " PRId64 " . \n " , disk - > mount_point , phy_file_last - > sub , disk - > mount_point , file - > sub , phy_last ) ;
2013-12-02 19:03:00 +01:00
}
2013-09-25 23:20:07 +02:00
+ + phy_dup ;
2013-12-02 19:03:00 +01:00
}
phy_file_last = file ;
2013-09-25 23:20:07 +02:00
phy_last = file - > physical ;
}
2011-05-24 21:49:15 +02:00
/* next node */
node = node - > next ;
2026-08-17 23:57:56 +02:00
/* insert the delayed containers before allocating the file */
2026-07-16 23:22:15 +02:00
if ( file_flag_has ( file , FILE_IS_MODIFIED_NEW ) ) {
2026-08-17 23:57:56 +02:00
/*
* The inode was already inserted in Phase 1 , so later paths with the same
* inode could be recognized as hardlinks . Now that Phase 2 removed the old
* version , insert only the new path / stamp entries .
*/
scan_file_stamp_insert ( scan , file ) ;
} else if ( file_flag_has ( file , FILE_IS_REALLOC_NEW ) ) {
scan_file_inode_insert ( scan , file ) ;
scan_file_stamp_insert ( scan , file ) ;
2026-07-16 23:22:15 +02:00
}
2015-10-01 18:19:48 +02:00
/* insert in the parity */
scan_file_allocate ( scan , file ) ;
2011-05-24 21:49:15 +02:00
}
2026-04-27 15:43:06 +02:00
/*
* Mark the disk without reliable physical offset if it has duplicates
* here it should never happen because we already sorted out hardlinks
*/
2013-09-25 23:20:07 +02:00
if ( state - > opt . force_order = = SORT_PHYSICAL & & phy_dup > 0 ) {
2013-12-14 17:54:04 +01:00
disk - > has_unreliable_physical = 1 ;
2013-09-25 23:20:07 +02:00
}
2011-08-26 14:18:38 +02:00
/* insert all the new links */
2011-09-23 21:48:50 +02:00
node = scan - > link_insert_list ;
2011-08-26 14:18:38 +02:00
while ( node ) {
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink = node - > data ;
2011-08-26 14:18:38 +02:00
/* next node */
node = node - > next ;
/* insert it */
2015-10-08 22:19:52 +02:00
scan_link_insert ( scan , slink ) ;
2011-08-26 14:18:38 +02:00
}
2013-01-27 16:44:04 +01:00
/* insert all the new dirs */
node = scan - > dir_insert_list ;
while ( node ) {
struct snapraid_dir * dir = node - > data ;
/* next node */
node = node - > next ;
/* insert it */
2014-02-12 20:02:35 +01:00
scan_emptydir_insert ( scan , dir ) ;
2013-01-27 16:44:04 +01:00
}
2011-09-03 10:12:13 +02:00
}
2021-11-28 15:50:38 +01:00
/* propagate the state change (after all the scan operations are called) */
for ( i = scanlist ; i ! = 0 ; i = i - > next ) {
struct snapraid_scan * scan = i - > data ;
if ( scan - > need_write ) {
state - > need_write = 1 ;
}
}
2015-10-07 22:07:20 +02:00
/* check for disks where all the previously existing files where removed */
2013-06-29 18:11:36 +02:00
if ( ! state - > opt . force_empty ) {
2015-04-30 22:05:53 +02:00
int all_missing = 0 ;
int all_rewritten = 0 ;
2014-04-03 23:02:43 +02:00
done = 0 ;
2026-07-30 12:40:08 +02:00
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist , j = scanlist ; i ! = 0 ; i = i - > next , j = j - > next ) {
2026-07-30 12:40:08 +02:00
assert ( j ! = 0 ) ; /* silence the clang static analyzer (both lists have the same number of elements) */
2011-09-23 21:48:50 +02:00
struct snapraid_disk * disk = i - > data ;
struct snapraid_scan * scan = j - > data ;
2011-09-03 10:19:18 +02:00
2015-04-30 22:05:53 +02:00
if ( scan - > count_equal = = 0
& & scan - > count_move = = 0
& & scan - > count_restore = = 0
& & ( scan - > count_remove ! = 0 | | scan - > count_change ! = 0 )
) {
2013-09-25 23:20:07 +02:00
if ( ! done ) {
done = 1 ;
2026-04-21 15:19:25 +02:00
log_error ( ESOFT , " WARNING! All the files previously present in disk '%s' at dir '%s' " , disk - > name , disk - > mount_point ) ;
2011-09-03 10:19:18 +02:00
} else {
2026-04-21 15:19:25 +02:00
log_error ( ESOFT , " , disk '%s' at dir '%s' " , disk - > name , disk - > mount_point ) ;
2011-09-03 10:19:18 +02:00
}
2015-04-30 22:05:53 +02:00
/* detect the special condition of all files missing */
if ( scan - > count_change = = 0 )
all_missing = 1 ;
/* detect the special condition of all files rewritten */
if ( scan - > count_remove = = 0 )
all_rewritten = 1 ;
2011-03-04 20:59:21 +01:00
}
}
2013-09-25 23:20:07 +02:00
if ( done ) {
2026-07-30 16:25:28 +02:00
log_error ( ESOFT , " \n are now missing or have been rewritten! \n " ) ;
2015-04-30 22:05:53 +02:00
if ( all_rewritten ) {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " This could occur when restoring a disk from a backup \n " ) ;
log_error ( ESOFT , " program that is not setting correctly the timestamps. \n " ) ;
2015-04-30 22:05:53 +02:00
}
if ( all_missing ) {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " This could occur when some disks are not mounted \n " ) ;
log_error ( ESOFT , " in the expected directory. \n " ) ;
2015-04-30 22:05:53 +02:00
}
2014-10-25 17:47:22 +02:00
if ( ! is_diff ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " If you want to '%s' anyway, use 'snapraid --force-empty %s'. \n " , state - > command , state - > command ) ;
2014-10-25 17:47:22 +02:00
exit ( EXIT_FAILURE ) ;
}
2011-09-03 10:19:18 +02:00
}
2011-09-03 10:12:13 +02:00
}
2011-03-04 20:59:21 +01:00
2015-10-07 22:07:20 +02:00
/* check for disks without the physical offset support */
2013-09-25 23:20:07 +02:00
if ( state - > opt . force_order = = SORT_PHYSICAL ) {
2014-04-03 23:02:43 +02:00
done = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2013-09-25 23:20:07 +02:00
struct snapraid_disk * disk = i - > data ;
2013-12-14 17:54:04 +01:00
if ( disk - > has_unreliable_physical ) {
2013-09-25 23:20:07 +02:00
if ( ! done ) {
done = 1 ;
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " WARNING! Physical offsets not supported for disk '%s' " , disk - > name ) ;
2013-09-25 23:20:07 +02:00
} else {
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " , '%s' " , disk - > name ) ;
2013-09-25 23:20:07 +02:00
}
}
}
if ( done ) {
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " . The order of files won't be optimal. \n " ) ;
2013-09-25 23:20:07 +02:00
}
}
2015-10-07 22:07:20 +02:00
/* check for disks without persistent inodes */
2014-04-03 23:02:43 +02:00
done = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-04-03 23:02:43 +02:00
struct snapraid_disk * disk = i - > data ;
2013-12-14 17:54:04 +01:00
2014-04-03 23:02:43 +02:00
if ( disk - > has_volatile_inodes ) {
if ( ! done ) {
done = 1 ;
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " WARNING! Inodes are not persistent for disks: '%s' " , disk - > name ) ;
2014-04-03 23:02:43 +02:00
} else {
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " , '%s' " , disk - > name ) ;
2013-12-14 17:54:04 +01:00
}
}
2014-04-03 23:02:43 +02:00
}
if ( done ) {
2026-07-30 16:25:28 +02:00
log_info ( ESOFT , " . Inodes are not used to detect move operations. \n " ) ;
2013-12-14 17:54:04 +01:00
}
2015-10-07 22:07:20 +02:00
/* check for disks with changed UUID */
2014-04-03 23:02:43 +02:00
done = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-04-03 23:02:43 +02:00
struct snapraid_disk * disk = i - > data ;
2013-09-25 23:20:07 +02:00
2026-04-27 15:43:06 +02:00
/*
* Don ' t print the message if the UUID changed because before
* it was no set .
* This is the normal condition for an empty disk because it
* isn ' t stored
*/
2014-04-03 23:02:43 +02:00
if ( disk - > has_different_uuid & & ! disk - > had_empty_uuid ) {
if ( ! done ) {
done = 1 ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! UUID is changed for disks: '%s' " , disk - > name ) ;
2014-04-03 23:02:43 +02:00
} else {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " , '%s' " , disk - > name ) ;
2013-09-25 23:20:07 +02:00
}
}
2014-04-03 23:02:43 +02:00
}
if ( done ) {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " . Inodes are not used to detect move operations. \n " ) ;
2014-04-03 23:02:43 +02:00
}
2015-10-07 22:07:20 +02:00
/* check for disks with unsupported UUID */
2014-04-03 23:02:43 +02:00
done = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-04-03 23:02:43 +02:00
struct snapraid_disk * disk = i - > data ;
if ( disk - > has_unsupported_uuid ) {
if ( ! done ) {
done = 1 ;
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! UUID is unsupported for disks: '%s' " , disk - > name ) ;
2014-04-03 23:02:43 +02:00
} else {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " , '%s' " , disk - > name ) ;
2014-04-03 23:02:43 +02:00
}
2013-09-25 23:20:07 +02:00
}
}
2014-04-03 23:02:43 +02:00
if ( done ) {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " . Not using inodes to detect move operations. \n " ) ;
2014-04-03 23:02:43 +02:00
}
2015-02-22 14:31:03 +01:00
total . count_equal = 0 ;
total . count_move = 0 ;
total . count_copy = 0 ;
2026-03-14 17:48:30 +01:00
total . count_relocate = 0 ;
2015-02-22 14:31:03 +01:00
total . count_restore = 0 ;
total . count_change = 0 ;
total . count_remove = 0 ;
total . count_insert = 0 ;
2011-06-20 20:35:04 +02:00
2015-02-22 14:31:03 +01:00
for ( i = scanlist ; i ! = 0 ; i = i - > next ) {
struct snapraid_scan * scan = i - > data ;
total . count_equal + = scan - > count_equal ;
total . count_move + = scan - > count_move ;
total . count_copy + = scan - > count_copy ;
2026-03-14 17:48:30 +01:00
total . count_relocate + = scan - > count_relocate ;
2015-02-22 14:31:03 +01:00
total . count_restore + = scan - > count_restore ;
total . count_change + = scan - > count_change ;
total . count_remove + = scan - > count_remove ;
total . count_insert + = scan - > count_insert ;
}
2013-09-30 22:24:30 +02:00
2015-02-22 14:31:03 +01:00
if ( is_diff ) {
msg_status ( " \n " ) ;
msg = msg_status ;
} else {
msg = msg_verbose ;
}
2013-09-30 22:24:30 +02:00
2015-02-22 14:31:03 +01:00
msg ( " %8u equal \n " , total . count_equal ) ;
2016-02-14 10:37:44 +01:00
msg ( " %8u added \n " , total . count_insert ) ;
msg ( " %8u removed \n " , total . count_remove ) ;
msg ( " %8u updated \n " , total . count_change ) ;
2015-02-22 14:31:03 +01:00
msg ( " %8u moved \n " , total . count_move ) ;
msg ( " %8u copied \n " , total . count_copy ) ;
2026-03-14 17:48:30 +01:00
msg ( " %8u relocated \n " , total . count_relocate ) ;
2015-02-22 14:31:03 +01:00
msg ( " %8u restored \n " , total . count_restore ) ;
2015-04-20 22:07:42 +02:00
log_tag ( " summary:equal:%u \n " , total . count_equal ) ;
2016-02-14 10:37:44 +01:00
log_tag ( " summary:added:%u \n " , total . count_insert ) ;
log_tag ( " summary:removed:%u \n " , total . count_remove ) ;
log_tag ( " summary:updated:%u \n " , total . count_change ) ;
2015-04-20 22:07:42 +02:00
log_tag ( " summary:moved:%u \n " , total . count_move ) ;
log_tag ( " summary:copied:%u \n " , total . count_copy ) ;
2026-03-14 17:48:30 +01:00
log_tag ( " summary:relocated:%u \n " , total . count_relocate ) ;
2015-04-20 22:07:42 +02:00
log_tag ( " summary:restored:%u \n " , total . count_restore ) ;
2026-02-09 15:50:58 +01:00
log_tag ( " list:scan_end \n " ) ;
2015-02-22 14:31:03 +01:00
2026-02-10 11:28:11 +01:00
/* save in the state */
state - > removed_files = total . count_remove ;
state - > updated_files = total . count_change ;
2026-03-14 17:48:30 +01:00
no_difference = ! total . count_move & & ! total . count_copy & & ! total . count_relocate & & ! total . count_restore
2015-02-22 14:31:03 +01:00
& & ! total . count_change & & ! total . count_remove & & ! total . count_insert ;
if ( is_diff ) {
2026-02-01 23:12:38 +01:00
if ( ! no_difference ) {
2015-02-22 14:31:03 +01:00
msg_status ( " There are differences! \n " ) ;
2026-02-01 23:12:38 +01:00
} else {
msg_status ( " No differences \n " ) ;
2011-06-20 20:35:04 +02:00
}
2026-02-02 16:32:16 +01:00
if ( state - > unsynced_blocks ! = 0 )
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " The last sync was interrupted. Run it again! \n " ) ;
2011-03-19 00:34:01 +01:00
2026-02-10 23:15:49 +01:00
if ( state - > unsynced_blocks ! = 0 ) {
log_tag ( " summary:exit:unsynced \n " ) ;
} else if ( ! no_difference ) {
log_tag ( " summary:exit:diff \n " ) ;
} else {
log_tag ( " summary:exit:equal \n " ) ;
}
2015-02-22 14:31:03 +01:00
}
2026-02-10 23:15:49 +01:00
2015-04-20 22:07:42 +02:00
log_flush ( ) ;
2015-02-22 14:31:03 +01:00
2026-08-24 23:03:00 +02:00
tommy_list_foreach ( & scanlist , scan_free ) ;
2015-03-02 23:01:15 +01:00
2016-01-02 16:52:20 +01:00
/* check the file-system on all disks */
2015-10-05 21:40:44 +02:00
state_fscheck ( state , " after scan " ) ;
2015-03-02 23:01:15 +01:00
if ( is_diff ) {
/* check for file difference */
if ( ! no_difference )
2015-04-28 19:15:16 +02:00
return 1 ;
2015-03-02 23:01:15 +01:00
/* check also for incomplete "sync" */
2026-02-02 16:32:16 +01:00
if ( state - > unsynced_blocks ! = 0 )
2015-04-28 19:15:16 +02:00
return 1 ;
2015-03-02 23:01:15 +01:00
}
return 0 ;
}
int state_diff ( struct snapraid_state * state )
{
return state_diffscan ( state , 1 ) ;
}
void state_scan ( struct snapraid_state * state )
{
( void ) state_diffscan ( state , 0 ) ; /* ignore return value */
2011-03-04 20:59:21 +01:00
}