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
# include "elem.h"
2013-02-27 21:07:16 +01:00
# include "import.h"
2014-10-19 21:07:17 +02:00
# include "search.h"
2011-03-04 20:59:21 +01:00
# include "state.h"
2015-01-02 18:52:04 +01:00
# include "support.h"
2013-07-01 23:08:25 +02:00
# include "parity.h"
2015-02-21 09:00:34 +01:00
# include "stream.h"
2016-01-20 21:30:00 +01:00
# include "handle.h"
2016-01-27 20:37:31 +01:00
# include "io.h"
2025-09-21 19:54:10 +02:00
# include "thermal.h"
2013-12-28 09:10:00 +01:00
# include "raid/raid.h"
# include "raid/cpu.h"
2011-03-04 20:59:21 +01:00
2016-01-01 17:42:37 +01:00
/**
* Configure the multithread support .
*
* Multi thread for write could be either faster or slower , depending
* on the specific conditions . With multithreads it ' s likely faster
* writing to disk , but you ' ll need to access multiple times the same data ,
* being potentially slower .
*
* Multi thread for verify is instead always generally faster ,
* so we enable it if possible .
*/
2021-05-25 22:08:12 +02:00
# if HAVE_THREAD
2016-01-01 17:42:37 +01:00
/* #define HAVE_MT_WRITE 1 */
# define HAVE_MT_VERIFY 1
# endif
2026-09-09 20:39:33 +02:00
/*
* Start content - file writeback in bounded windows to avoid dirty - page
* throttling ; the subsequent fsync ( ) remains responsible for durability .
*
* Best test - rewrite serialization / write times with four 4 GB content files :
* ADVISE_SEQUENTIAL : 128 s
* ADVISE_FLUSH : 86 s
* ADVISE_DISCARD_WINDOW : 73 s
* ADVISE_FLUSH_WINDOW : 61 s
*/
# define CONTENT_WRITE_ADVISE ADVISE_FLUSH_WINDOW
2013-09-15 21:12:41 +02:00
const char * lev_name ( unsigned l )
{
switch ( l ) {
case 0 : return " Parity " ;
2013-11-17 14:12:57 +01:00
case 1 : return " 2-Parity " ;
case 2 : return " 3-Parity " ;
case 3 : return " 4-Parity " ;
case 4 : return " 5-Parity " ;
case 5 : return " 6-Parity " ;
2013-09-15 21:12:41 +02:00
}
2026-01-08 11:18:11 +01:00
return " invalid " ;
2013-09-15 21:12:41 +02:00
}
const char * lev_config_name ( unsigned l )
{
switch ( l ) {
case 0 : return " parity " ;
2013-11-17 14:12:57 +01:00
case 1 : return " 2-parity " ;
case 2 : return " 3-parity " ;
case 3 : return " 4-parity " ;
case 4 : return " 5-parity " ;
case 5 : return " 6-parity " ;
2013-09-15 21:12:41 +02:00
}
2026-01-08 11:18:11 +01:00
return " invalid " ;
2013-09-15 21:12:41 +02:00
}
2015-04-08 22:56:44 +02:00
static int lev_config_scan ( const char * s , unsigned * level , unsigned * mode )
{
if ( strcmp ( s , " parity " ) = = 0 | | strcmp ( s , " 1-parity " ) = = 0 ) {
* level = 0 ;
return 0 ;
}
if ( strcmp ( s , " q-parity " ) = = 0 | | strcmp ( s , " 2-parity " ) = = 0 ) {
* level = 1 ;
return 0 ;
}
if ( strcmp ( s , " r-parity " ) = = 0 | | strcmp ( s , " 3-parity " ) = = 0 ) {
* level = 2 ;
return 0 ;
}
if ( strcmp ( s , " 4-parity " ) = = 0 ) {
* level = 3 ;
return 0 ;
}
if ( strcmp ( s , " 5-parity " ) = = 0 ) {
* level = 4 ;
return 0 ;
}
if ( strcmp ( s , " 6-parity " ) = = 0 ) {
* level = 5 ;
return 0 ;
}
if ( strcmp ( s , " z-parity " ) = = 0 ) {
* level = 2 ;
if ( mode )
2026-07-29 12:47:44 +02:00
* mode = RAID_MODE_VANDERMONDE_RAID ;
2015-04-08 22:56:44 +02:00
return 0 ;
}
return - 1 ;
}
2013-11-05 19:40:32 +01:00
const char * lev_raid_name ( unsigned mode , unsigned n )
2013-10-25 18:03:05 +02:00
{
switch ( n ) {
2013-11-13 21:35:44 +01:00
case 1 : return " par1 " ;
case 2 : return " par2 " ;
2026-07-29 12:47:44 +02:00
case 3 : if ( mode = = RAID_MODE_CAUCHY_RAID | | mode = = RAID_MODE_CAUCHY_AES )
2013-11-13 21:35:44 +01:00
return " par3 " ;
2013-11-05 19:40:32 +01:00
else
2013-11-17 14:27:31 +01:00
return " parz " ;
2013-11-13 21:35:44 +01:00
case 4 : return " par4 " ;
case 5 : return " par5 " ;
case 6 : return " par6 " ;
2013-10-25 18:03:05 +02:00
}
return 0 ;
}
2011-03-04 20:59:21 +01:00
void state_init ( struct snapraid_state * state )
{
2025-09-15 16:56:09 +02:00
unsigned l , s , i ;
2013-09-15 21:12:41 +02:00
2013-12-24 21:46:00 +01:00
memset ( & state - > opt , 0 , sizeof ( state - > opt ) ) ;
2026-03-16 12:00:53 +01:00
state - > mapped_device = 0 ;
2026-04-21 15:19:25 +02:00
state - > snapshot = 0 ;
2011-12-08 22:40:30 +01:00
state - > filter_hidden = 0 ;
2012-03-13 23:59:59 +01:00
state - > autosave = 0 ;
2011-03-19 22:00:08 +01:00
state - > need_write = 0 ;
2025-10-13 00:20:36 +02:00
state - > written = 0 ;
2014-06-15 18:10:18 +02:00
state - > checked_read = 0 ;
2016-01-16 07:27:28 +01:00
state - > block_size = 256 * KIBI ; /* default 256 KiB */
2026-07-29 12:47:44 +02:00
state - > raid_mode = RAID_MODE_CAUCHY_RAID ;
2016-05-15 14:18:44 +02:00
state - > file_mode = ADVISE_DEFAULT ;
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < LEV_MAX ; + + l ) {
2016-04-26 21:48:39 +02:00
state - > parity [ l ] . split_mac = 0 ;
for ( s = 0 ; s < SPLIT_MAX ; + + s ) {
state - > parity [ l ] . split_map [ s ] . path [ 0 ] = 0 ;
state - > parity [ l ] . split_map [ s ] . uuid [ 0 ] = 0 ;
2026-06-28 22:47:58 +02:00
state - > parity [ l ] . split_map [ s ] . content_uuid [ 0 ] = 0 ;
2026-01-29 18:43:00 +01:00
state - > parity [ l ] . split_map [ s ] . fstype [ 0 ] = 0 ;
state - > parity [ l ] . split_map [ s ] . fslabel [ 0 ] = 0 ;
2016-04-26 21:48:39 +02:00
state - > parity [ l ] . split_map [ s ] . size = PARITY_SIZE_INVALID ;
state - > parity [ l ] . split_map [ s ] . device = 0 ;
}
2015-04-08 23:10:43 +02:00
state - > parity [ l ] . smartctl [ 0 ] = 0 ;
2026-06-15 14:41:03 +02:00
state - > parity [ l ] . smartctl_info [ 0 ] = 0 ;
2026-06-25 22:52:53 +02:00
for ( i = 0 ; i < SMART_IGNORE_MAX ; + + i ) {
state - > parity [ l ] . smartignore [ i ] . attr_index = 0 ;
state - > parity [ l ] . smartignore [ i ] . attr_name [ 0 ] = 0 ;
}
2014-11-09 19:14:29 +01:00
state - > parity [ l ] . total_blocks = 0 ;
state - > parity [ l ] . free_blocks = 0 ;
2016-04-26 21:48:39 +02:00
state - > parity [ l ] . skip_access = 0 ;
2014-11-09 19:14:29 +01:00
state - > parity [ l ] . tick = 0 ;
2017-11-19 19:33:47 +01:00
state - > parity [ l ] . cached_blocks = 0 ;
2017-03-26 18:10:46 +02:00
state - > parity [ l ] . is_excluded_by_filter = 0 ;
2013-09-15 21:12:41 +02:00
}
2014-09-09 22:22:46 +02:00
state - > tick_io = 0 ;
2016-01-26 23:25:01 +01:00
state - > tick_misc = 0 ;
2016-01-31 16:38:29 +01:00
state - > tick_sched = 0 ;
2016-01-26 23:25:01 +01:00
state - > tick_raid = 0 ;
state - > tick_hash = 0 ;
2026-03-02 01:13:19 +01:00
state - > tick_last = os_tick ( ) ;
2013-12-18 19:26:57 +01:00
state - > share [ 0 ] = 0 ;
2013-02-22 22:17:54 +01:00
state - > pool [ 0 ] = 0 ;
state - > pool_device = 0 ;
2013-06-18 21:06:31 +02:00
state - > lockfile [ 0 ] = 0 ;
2011-04-23 21:38:26 +02:00
state - > level = 1 ; /* default is the lowest protection */
2013-09-21 15:53:46 +02:00
state - > no_conf = 0 ;
2026-06-25 22:52:53 +02:00
for ( i = 0 ; i < SMART_IGNORE_MAX ; + + i ) {
state - > smartignore [ i ] . attr_index = 0 ;
state - > smartignore [ i ] . attr_name [ 0 ] = 0 ;
}
2026-02-08 22:56:22 +01:00
state - > rehash_blocks = 0 ;
state - > bad_blocks = 0 ;
2026-02-02 16:32:16 +01:00
state - > unsynced_blocks = 0 ;
2026-02-08 22:56:22 +01:00
state - > unscrubbed_blocks = 0 ;
2025-09-21 19:54:10 +02:00
state - > thermal_stop_gathering = 0 ;
2026-09-07 11:38:49 +02:00
state - > thermal_system_temperature = 0 ;
2025-09-21 19:54:10 +02:00
state - > thermal_ambient_temperature = 0 ;
state - > thermal_highest_temperature = 0 ;
state - > thermal_first = 0 ;
state - > thermal_latest = 0 ;
state - > thermal_temperature_limit = 0 ;
state - > thermal_cooldown_time = 0 ;
2013-06-09 22:55:57 +02:00
2011-09-23 21:48:50 +02:00
tommy_list_init ( & state - > disklist ) ;
2026-03-11 13:53:42 +01:00
tommy_list_init ( & state - > extralist ) ;
2011-10-01 19:18:32 +02:00
tommy_list_init ( & state - > maplist ) ;
2011-04-25 13:43:51 +02:00
tommy_list_init ( & state - > contentlist ) ;
2011-04-26 18:51:42 +02:00
tommy_list_init ( & state - > filterlist ) ;
2013-02-27 21:07:16 +01:00
tommy_list_init ( & state - > importlist ) ;
2025-09-21 19:54:10 +02:00
tommy_list_init ( & state - > thermallist ) ;
2013-02-27 21:07:16 +01:00
tommy_hashdyn_init ( & state - > importset ) ;
2013-07-01 23:08:25 +02:00
tommy_hashdyn_init ( & state - > previmportset ) ;
2014-10-19 21:07:17 +02:00
tommy_hashdyn_init ( & state - > searchset ) ;
2013-09-28 09:40:08 +02:00
tommy_arrayblkof_init ( & state - > infoarr , sizeof ( snapraid_info ) ) ;
2026-02-08 22:56:22 +01:00
tommy_list_init ( & state - > bucketlist ) ;
2011-03-04 20:59:21 +01:00
}
void state_done ( struct snapraid_state * state )
{
2026-08-24 23:03:00 +02:00
tommy_list_foreach ( & state - > disklist , disk_free ) ;
tommy_list_foreach ( & state - > extralist , extra_free ) ;
tommy_list_foreach ( & state - > maplist , map_free ) ;
tommy_list_foreach ( & state - > contentlist , content_free ) ;
tommy_list_foreach ( & state - > filterlist , filter_free ) ;
tommy_list_foreach ( & state - > importlist , import_file_free ) ;
tommy_list_foreach ( & state - > thermallist , thermal_free ) ;
tommy_hashdyn_foreach ( & state - > searchset , search_file_free ) ;
2013-02-27 21:07:16 +01:00
tommy_hashdyn_done ( & state - > importset ) ;
2013-07-01 23:08:25 +02:00
tommy_hashdyn_done ( & state - > previmportset ) ;
2014-10-19 21:07:17 +02:00
tommy_hashdyn_done ( & state - > searchset ) ;
2013-09-28 09:40:08 +02:00
tommy_arrayblkof_done ( & state - > infoarr ) ;
2026-08-24 23:03:00 +02:00
tommy_list_foreach ( & state - > bucketlist , bucket_free ) ;
2011-03-04 20:59:21 +01:00
}
2011-10-01 19:18:32 +02:00
/**
2015-10-07 22:07:20 +02:00
* Check the configuration .
2011-10-01 19:18:32 +02:00
*/
2013-10-30 21:45:53 +01:00
static void state_config_check ( struct snapraid_state * state , const char * path , tommy_list * filterlist_disk )
2011-10-01 19:18:32 +02:00
{
2012-01-31 21:53:26 +01:00
tommy_node * i ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2026-08-26 20:59:56 +02:00
size_t disk_count ;
/* check total memory budget for block buffers (max 256 MiB) */
disk_count = tommy_list_count ( & state - > disklist ) + state - > level ;
if ( disk_count > ( 256 * 1024 ) / ( state - > block_size / KIBI ) ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " The combination of %zu disks and %u KiB 'blocksize' in '%s' exceeds the 256 MiB limit. \n " ,
disk_count , state - > block_size / KIBI , path ) ;
log_fatal ( EUSER , " Please reduce the 'blocksize' or the number of disks. \n " ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2012-01-31 21:53:26 +01:00
2013-11-05 19:40:32 +01:00
/* check for parity level */
2026-07-29 12:47:44 +02:00
if ( state - > raid_mode = = RAID_MODE_VANDERMONDE_RAID ) {
2013-12-27 13:56:56 +01:00
if ( state - > level > 3 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Using z-parity limits you to a maximum of 3 parities. \n " ) ;
2013-11-04 23:47:49 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STIO */
2013-11-04 23:47:49 +01:00
}
}
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
if ( state - > parity [ l ] . split_mac = = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Missing '%s' specification in '%s' \n " , lev_config_name ( l ) , path ) ;
2013-09-15 21:12:41 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-09-15 21:12:41 +02:00
}
2013-09-11 23:30:32 +02:00
}
2011-10-01 19:18:32 +02:00
if ( tommy_list_empty ( & state - > contentlist ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Missing 'content' specification in '%s' \n " , path ) ;
2011-10-01 19:18:32 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-01 19:18:32 +02:00
}
2015-10-07 22:07:20 +02:00
/* check for equal paths */
2014-09-27 00:32:39 +02:00
for ( i = state - > contentlist ; i ! = 0 ; i = i - > next ) {
2012-01-31 21:53:26 +01:00
struct snapraid_content * content = i - > data ;
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
if ( pathcmp ( state - > parity [ l ] . split_map [ s ] . path , content - > content ) = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Same path used for '%s' and 'content' as '%s' \n " , lev_config_name ( l ) , content - > content ) ;
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-09-15 21:12:41 +02:00
}
2012-01-31 21:53:26 +01:00
}
}
2014-11-09 19:14:29 +01:00
/* check device of data disks */
if ( ! state - > opt . skip_device & & ! state - > opt . skip_disk_access ) {
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2011-10-01 19:18:32 +02:00
tommy_node * j ;
2012-03-24 09:14:21 +01:00
struct snapraid_disk * disk = i - > data ;
2011-10-01 19:18:32 +02:00
2015-10-16 23:17:38 +02:00
/* skip data disks that are not accessible */
if ( disk - > skip_access )
continue ;
2012-03-21 21:57:43 +01:00
# ifdef _WIN32
2026-08-26 08:36:19 +02:00
if ( disk - > mount_device = = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Disk '%s' has a zero serial number. \n " , disk - > dir ) ;
log_fatal ( ESOFT , " This is not necessarily wrong, but for using SnapRAID \n " ) ;
log_fatal ( ESOFT , " it's better to change the serial number of the disk. \n " ) ;
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich'. \n " ) ;
2012-03-24 09:14:21 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2012-03-21 21:57:43 +01:00
}
# endif
2011-10-01 19:18:32 +02:00
2015-10-16 23:17:38 +02:00
for ( j = i - > next ; j ! = 0 ; j = j - > next ) {
struct snapraid_disk * other = j - > data ;
2026-04-28 00:35:15 +02:00
if ( disk - > mount_device = = other - > mount_device ) {
2015-10-16 23:17:38 +02:00
if ( state - > opt . force_device ) {
2026-04-27 15:43:06 +02:00
/*
* Note that we just ignore the issue
* and we DON ' T mark the disk to be skipped
* because we want to use these disks
*/
2015-11-24 18:49:20 +01:00
if ( ! state - > opt . no_warnings )
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " DANGER! Ignoring that disks '%s' and '%s' are on the same device \n " , disk - > name , other - > name ) ;
2015-10-16 23:17:38 +02:00
} else {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-04-21 15:19:25 +02:00
log_fatal ( ESOFT , " Disks '%s' and '%s' are on the same device. \n " , disk - > mount_point , other - > mount_point ) ;
2013-06-08 14:53:59 +02:00
# ifdef _WIN32
2026-08-26 08:36:19 +02:00
log_fatal ( ESOFT , " Both have the serial number '% " PRIx64 " '. \n " , disk - > mount_device ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich' \n " ) ;
log_fatal ( ESOFT , " to change one of the disk serial. \n " ) ;
2013-06-08 14:53:59 +02:00
# endif
2013-07-26 14:50:52 +02:00
/* in "fix" we allow to continue anyway */
if ( strcmp ( state - > command , " fix " ) = = 0 ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " You can '%s' anyway, using 'snapraid --force-device %s'. \n " , state - > command , state - > command ) ;
2013-07-26 14:50:52 +02:00
}
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-07-26 14:50:52 +02:00
}
2011-10-01 19:18:32 +02:00
}
}
2026-05-20 00:17:18 +02:00
2015-10-16 23:17:38 +02:00
2014-11-09 19:14:29 +01:00
if ( ! state - > opt . skip_parity_access ) {
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2026-04-28 00:35:15 +02:00
if ( disk - > mount_device = = state - > parity [ l ] . split_map [ s ] . device ) {
2019-05-05 09:17:05 +02:00
if ( state - > opt . force_device ) {
2026-04-27 15:43:06 +02:00
/*
* Note that we just ignore the issue
* and we DON ' T mark the disk to be skipped
* because we want to use these disks
*/
2019-05-05 09:17:05 +02:00
if ( ! state - > opt . no_warnings )
2026-04-21 15:19:25 +02:00
log_fatal ( EUSER , " DANGER! Ignoring that disks '%s' and %s '%s' are on the same device \n " , disk - > mount_point , lev_name ( l ) , state - > parity [ l ] . split_map [ s ] . path ) ;
2019-05-05 09:17:05 +02:00
} else {
/* LCOV_EXCL_START */
2026-04-21 15:19:25 +02:00
log_fatal ( ESOFT , " Disk '%s' and %s '%s' are on the same device. \n " , disk - > mount_point , lev_name ( l ) , state - > parity [ l ] . split_map [ s ] . path ) ;
2013-06-08 14:53:59 +02:00
# ifdef _WIN32
2026-08-26 08:36:19 +02:00
log_fatal ( ESOFT , " Both have the serial number '% " PRIx64 " '. \n " , disk - > mount_device ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich' \n " ) ;
log_fatal ( ESOFT , " to change one of the disk serial. \n " ) ;
2013-06-08 14:53:59 +02:00
# endif
2019-05-05 09:17:05 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-04-26 21:48:39 +02:00
}
2014-11-09 19:14:29 +01:00
}
2013-09-15 21:12:41 +02:00
}
2011-10-01 19:18:32 +02:00
}
2012-03-13 21:24:34 +01:00
2026-04-28 00:35:15 +02:00
if ( state - > pool [ 0 ] ! = 0 & & disk - > mount_device = = state - > pool_device ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-04-21 15:19:25 +02:00
log_fatal ( ESOFT , " Disk '%s' and pool '%s' are on the same device. \n " , disk - > mount_point , state - > pool ) ;
2013-06-08 14:53:59 +02:00
# ifdef _WIN32
2026-08-26 08:36:19 +02:00
log_fatal ( ESOFT , " Both have the serial number '% " PRIx64 " '. \n " , disk - > mount_device ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich' \n " ) ;
log_fatal ( ESOFT , " to change one of the disk serial. \n " ) ;
2013-06-08 14:53:59 +02:00
# endif
2013-02-22 22:17:54 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-22 22:17:54 +01:00
}
2012-03-13 21:24:34 +01:00
}
2014-11-09 19:14:29 +01:00
}
2012-03-13 21:24:34 +01:00
2014-11-09 19:14:29 +01:00
/* check device of parity disks */
if ( ! state - > opt . skip_device & & ! state - > opt . skip_parity_access ) {
2014-09-27 00:32:39 +02:00
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2017-11-03 23:46:45 +01:00
unsigned j , t ;
2015-10-16 23:17:38 +02:00
2016-04-26 21:48:39 +02:00
/* skip parity disks that are not accessible */
if ( state - > parity [ l ] . skip_access )
continue ;
2015-10-16 23:17:38 +02:00
# ifdef _WIN32
2016-04-26 21:48:39 +02:00
if ( state - > parity [ l ] . split_map [ s ] . device = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Disk '%s' has a zero serial number. \n " , state - > parity [ l ] . split_map [ s ] . path ) ;
log_fatal ( ESOFT , " This is not necessarily wrong, but for using SnapRAID \n " ) ;
log_fatal ( ESOFT , " it's better to change the serial number of the disk. \n " ) ;
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich'. \n " ) ;
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2012-03-21 21:57:43 +01:00
# endif
2012-03-24 09:14:21 +01:00
2016-04-26 21:48:39 +02:00
for ( j = l + 1 ; j < state - > level ; + + j ) {
for ( t = 0 ; t < state - > parity [ j ] . split_mac ; + + t ) {
if ( state - > parity [ l ] . split_map [ s ] . device = = state - > parity [ j ] . split_map [ t ] . device ) {
if ( state - > opt . force_device ) {
2026-04-27 15:43:06 +02:00
/*
* Note that we just ignore the issue
* and we DON ' T mark the disk to be skipped
* because we want to use these disks
*/
2016-04-26 21:48:39 +02:00
if ( ! state - > opt . no_warnings )
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " DANGER! Skipping parities '%s' and '%s' on the same device \n " , lev_config_name ( l ) , lev_config_name ( j ) ) ;
2016-04-26 21:48:39 +02:00
} else {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Parity '%s' and '%s' are on the same device. \n " , state - > parity [ l ] . split_map [ s ] . path , state - > parity [ j ] . split_map [ t ] . path ) ;
2013-06-08 14:53:59 +02:00
# ifdef _WIN32
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Both have the serial number '% " PRIx64 " '. \n " , state - > parity [ l ] . split_map [ s ] . device ) ;
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich' \n " ) ;
log_fatal ( ESOFT , " to change one of the disk serial. \n " ) ;
2014-09-27 00:32:39 +02:00
# endif
2016-04-26 21:48:39 +02:00
/* in "fix" we allow to continue anyway */
if ( strcmp ( state - > command , " fix " ) = = 0 ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " You can '%s' anyway, using 'snapraid --force-device %s'. \n " , state - > command , state - > command ) ;
2016-04-26 21:48:39 +02:00
}
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-10-16 23:17:38 +02:00
}
}
2013-09-15 21:12:41 +02:00
}
2013-09-11 23:30:32 +02:00
2016-04-26 21:48:39 +02:00
if ( state - > pool [ 0 ] ! = 0 & & state - > pool_device = = state - > parity [ l ] . split_map [ s ] . device ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Pool '%s' and parity '%s' are on the same device. \n " , state - > pool , state - > parity [ l ] . split_map [ s ] . path ) ;
2013-09-11 23:30:32 +02:00
# ifdef _WIN32
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Both have the serial number '% " PRIx64 " '. \n " , state - > pool_device ) ;
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich' \n " ) ;
log_fatal ( ESOFT , " to change one of the disk serial. \n " ) ;
2013-06-08 14:53:59 +02:00
# endif
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-09-15 21:12:41 +02:00
}
2013-02-22 22:17:54 +01:00
}
2012-03-24 09:14:21 +01:00
}
2014-11-09 19:14:29 +01:00
/* check device of pool disk */
# ifdef _WIN32
if ( ! state - > opt . skip_device ) {
if ( state - > pool [ 0 ] ! = 0 & & state - > pool_device = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Disk '%s' has a zero serial number. \n " , state - > pool ) ;
log_fatal ( ESOFT , " This is not necessarily wrong, but for using SnapRAID \n " ) ;
log_fatal ( ESOFT , " it's better to change the serial number of the disk. \n " ) ;
log_fatal ( ESOFT , " Try using the 'VolumeID' tool by 'Mark Russinovich'. \n " ) ;
2014-11-09 19:14:29 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
# endif
2012-03-24 09:14:21 +01:00
/* count the content files */
2015-10-16 23:17:38 +02:00
if ( ! state - > opt . skip_device & & ! state - > opt . skip_content_access ) {
2012-03-24 09:14:21 +01:00
unsigned content_count ;
content_count = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > contentlist ; i ! = 0 ; i = i - > next ) {
2012-03-24 09:14:21 +01:00
tommy_node * j ;
struct snapraid_content * content = i - > data ;
/* check if there are others in the same disk */
2014-09-27 00:32:39 +02:00
for ( j = i - > next ; j ! = 0 ; j = j - > next ) {
2012-03-24 09:14:21 +01:00
struct snapraid_content * other = j - > data ;
if ( content - > device = = other - > device ) {
2026-05-30 17:54:31 +02:00
log_error ( ESOFT , " WARNING! Content files on the same disk: '%s' and '%s'. \n " , content - > content , other - > content ) ;
2012-03-24 09:14:21 +01:00
break ;
}
}
if ( j ! = 0 ) {
/* skip it */
continue ;
}
+ + content_count ;
}
2014-09-27 00:32:39 +02:00
if ( content_count < state - > level + 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " You must have at least %d 'content' files in different disks. \n " , state - > level + 1 ) ;
2011-10-01 19:18:32 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-01 19:18:32 +02:00
}
}
2013-10-25 18:03:05 +02:00
2013-11-05 19:40:32 +01:00
/* check for speed */
2013-12-31 15:46:24 +01:00
# ifdef CONFIG_X86
2013-12-27 13:56:56 +01:00
if ( ! raid_cpu_has_ssse3 ( ) )
2013-10-25 18:03:05 +02:00
# endif
2026-07-29 12:47:44 +02:00
if ( ( state - > raid_mode = = RAID_MODE_CAUCHY_RAID | | state - > raid_mode = = RAID_MODE_CAUCHY_AES ) & & ! state - > opt . no_warnings ) {
2013-11-05 19:40:32 +01:00
if ( state - > level = = 3 ) {
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " WARNING! Your CPU doesn't have a fast implementation for triple parity. \n " ) ;
log_info ( EUSER , " WARNING! It's recommended to switch to 'z-parity' instead than '3-parity'. \n " ) ;
2013-11-05 19:40:32 +01:00
} else if ( state - > level > 3 ) {
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " WARNING! Your CPU doesn't have a fast implementation beyond triple parity. \n " ) ;
log_info ( EUSER , " WARNING! It's recommended to reduce the parity levels to triple parity. \n " ) ;
2013-11-05 19:40:32 +01:00
}
2013-10-25 18:03:05 +02:00
}
2013-10-30 21:45:53 +01:00
/* ensure that specified filter disks are valid ones */
2014-09-27 00:32:39 +02:00
for ( i = tommy_list_head ( filterlist_disk ) ; i ! = 0 ; i = i - > next ) {
2013-10-30 21:45:53 +01:00
tommy_node * j ;
struct snapraid_filter * filter = i - > data ;
2014-09-27 00:32:39 +02:00
for ( j = state - > disklist ; j ! = 0 ; j = j - > next ) {
2013-10-30 21:45:53 +01:00
struct snapraid_disk * disk = j - > data ;
2026-01-10 16:57:04 +01:00
if ( wnmatch ( filter - > pattern , disk - > name ) = = 0 )
2013-10-30 21:45:53 +01:00
break ;
}
2026-03-11 13:53:42 +01:00
if ( j = = 0 ) {
for ( j = state - > extralist ; j ! = 0 ; j = j - > next ) {
struct snapraid_extra * extra = j - > data ;
if ( wnmatch ( filter - > pattern , extra - > name ) = = 0 )
break ;
}
}
2013-10-30 21:45:53 +01:00
if ( j = = 0 ) {
2015-10-17 17:36:40 +02:00
/* check matching with parity disks */
for ( l = 0 ; l < state - > level ; + + l )
2026-01-10 16:57:04 +01:00
if ( wnmatch ( filter - > pattern , lev_config_name ( l ) ) = = 0 )
2015-10-17 17:36:40 +02:00
break ;
if ( l = = state - > level ) {
/* LCOV_EXCL_START */
2026-03-11 13:53:42 +01:00
log_fatal ( EUSER , " Option -d, --filter-disk %s doesn't match any data or parity or extra disk. \n " , filter - > pattern ) ;
2015-10-17 17:36:40 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-10-30 21:45:53 +01:00
}
}
2011-10-01 19:18:32 +02:00
}
2015-04-10 18:29:06 +02:00
/**
2026-06-15 14:41:03 +02:00
* Parse the smartctl command .
2015-04-10 18:29:06 +02:00
*
2026-06-15 14:41:03 +02:00
* It extracts the optional [ info : xxx ] tag , validates , and returns
* the cleaned smartctl command and smartctl_info options .
2015-04-10 18:29:06 +02:00
*/
2026-06-15 14:41:03 +02:00
int parse_smartctl ( const char * custom , char * smartctl , size_t smartctl_size , char * smartctl_info , size_t smartctl_info_size )
2015-04-10 18:29:06 +02:00
{
2026-06-15 14:41:03 +02:00
const char * s ;
const char * info_start = 0 ;
size_t info_len = 0 ;
size_t smartctl_len = 0 ;
2015-04-10 18:29:06 +02:00
int arg = 0 ;
2026-06-15 14:41:03 +02:00
/* initialize output buffers */
smartctl [ 0 ] = 0 ;
smartctl_info [ 0 ] = 0 ;
/* look for "[info:" */
s = custom ;
while ( * s ) {
if ( strncmp ( s , " [info: " , 6 ) = = 0 ) {
if ( info_start ) {
/* multiple info tags not allowed */
return - 1 ;
}
info_start = s ;
s + = 6 ;
/* skip spaces inside bracket */
while ( isspace ( ( unsigned char ) * s ) )
+ + s ;
/* find matching ']' */
const char * val_start = s ;
while ( * s & & * s ! = ' ] ' )
+ + s ;
if ( * s ! = ' ] ' ) {
/* missing closing bracket */
return - 1 ;
}
/* trim trailing spaces inside bracket */
const char * val_end = s ;
while ( val_end > val_start & & isspace ( ( unsigned char ) val_end [ - 1 ] ) )
- - val_end ;
info_len = val_end - val_start ;
if ( info_len > = smartctl_info_size ) {
/* info string too long */
return - 1 ;
}
memcpy ( smartctl_info , val_start , info_len ) ;
smartctl_info [ info_len ] = 0 ;
+ + s ; /* skip ']' */
} else {
if ( smartctl_len + 1 > = smartctl_size ) {
return - 1 ;
}
smartctl [ smartctl_len + + ] = * s + + ;
}
}
smartctl [ smartctl_len ] = 0 ;
/* trim extra spaces from the smartctl options */
strtrim ( smartctl ) ;
/* validate the cleaned smartctl command */
s = smartctl ;
2015-04-10 18:29:06 +02:00
while ( * s ) {
if ( s [ 0 ] = = ' % ' & & s [ 1 ] = = ' s ' ) {
if ( arg ) {
/* LCOV_EXCL_START */
return - 1 ;
/* LCOV_EXCL_STOP */
}
arg = 1 ;
2026-06-15 14:41:03 +02:00
+ + s ;
2015-04-10 18:29:06 +02:00
} else if ( s [ 0 ] = = ' % ' ) {
/* LCOV_EXCL_START */
return - 1 ;
/* LCOV_EXCL_STOP */
}
2026-06-15 14:41:03 +02:00
+ + s ;
}
2015-04-10 18:29:06 +02:00
2026-06-15 14:41:03 +02:00
/* validate the smartctl_info command */
s = smartctl_info ;
while ( * s ) {
if ( s [ 0 ] = = ' % ' ) {
/* LCOV_EXCL_START */
return - 1 ;
/* LCOV_EXCL_STOP */
}
2015-04-10 18:29:06 +02:00
+ + s ;
}
return 0 ;
}
2013-10-30 21:45:53 +01:00
void state_config ( struct snapraid_state * state , const char * path , const char * command , struct snapraid_option * opt , tommy_list * filterlist_disk )
2011-03-04 20:59:21 +01:00
{
2011-06-26 11:10:51 +02:00
STREAM * f ;
2011-03-04 20:59:21 +01:00
unsigned line ;
2013-03-28 18:37:31 +01:00
tommy_node * i ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2013-06-29 18:11:36 +02:00
/* copy the options */
state - > opt = * opt ;
2026-09-07 21:16:55 +02:00
/* if unset, sort by directory order */
2013-06-29 18:11:36 +02:00
if ( ! state - > opt . force_order )
2026-09-07 21:16:55 +02:00
state - > opt . force_order = SORT_DIR ;
2014-09-04 22:52:04 +02:00
/* adjust file mode */
2016-05-15 14:18:44 +02:00
if ( state - > opt . file_mode ! = ADVISE_DEFAULT ) {
state - > file_mode = state - > opt . file_mode ;
} else {
/* default mode, if nothing is specified */
2017-11-24 20:52:05 +01:00
state - > file_mode = ADVISE_DISCARD ;
2016-05-15 14:18:44 +02:00
}
2014-09-04 22:52:04 +02:00
/* store current command */
2013-03-03 22:39:46 +01:00
state - > command = command ;
2011-03-04 20:59:21 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " conf:file:%s \n " , esc_tag ( path ) ) ;
2011-06-20 20:35:04 +02:00
2026-01-10 15:30:25 +01:00
f = sopen_read ( path , 0 ) ;
2011-03-04 20:59:21 +01:00
if ( ! f ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2011-03-04 20:59:21 +01:00
if ( errno = = ENOENT ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " No configuration file found at '%s' \n " , path ) ;
2011-03-04 20:59:21 +01:00
} else if ( errno = = EACCES ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " You do not have rights to access the configuration file '%s' \n " , path ) ;
2011-03-04 20:59:21 +01:00
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the configuration file '%s'. %s. \n " , path , strerror ( errno ) ) ;
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
}
2011-06-23 20:28:52 +02:00
line = 1 ;
2011-03-04 20:59:21 +01:00
while ( 1 ) {
2011-06-23 20:28:52 +02:00
char tag [ PATH_MAX ] ;
char buffer [ PATH_MAX ] ;
2026-06-04 00:34:51 +02:00
ssize_t ret ;
2011-06-23 20:28:52 +02:00
int c ;
2015-04-08 22:56:44 +02:00
unsigned level ;
2011-03-04 20:59:21 +01:00
2011-06-23 20:28:52 +02:00
/* skip initial spaces */
2011-06-26 11:10:51 +02:00
sgetspace ( f ) ;
2011-03-04 20:59:21 +01:00
2011-06-23 20:28:52 +02:00
/* read the command */
2011-06-26 11:10:51 +02:00
ret = sgettok ( f , tag , sizeof ( tag ) ) ;
2011-07-06 19:38:05 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Error reading the configuration file '%s' at line %u \n " , path , line ) ;
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
}
2011-06-23 20:28:52 +02:00
/* skip spaces after the command */
2011-06-26 11:10:51 +02:00
sgetspace ( f ) ;
2011-03-04 20:59:21 +01:00
2014-02-12 22:10:06 +01:00
if ( strcmp ( tag , " blocksize " ) = = 0
2014-09-27 00:32:39 +02:00
/* block_size is the old format of the option */
2014-02-12 22:10:06 +01:00
| | strcmp ( tag , " block_size " ) = = 0 ) {
2011-06-26 11:10:51 +02:00
ret = sgetu32 ( f , & state - > block_size ) ;
2011-07-06 19:38:05 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'blocksize' specification in '%s' at line %u \n " , path , line ) ;
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
}
if ( state - > block_size < 1 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too small 'blocksize' specification in '%s' at line %u \n " , path , line ) ;
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
}
2016-01-16 07:27:28 +01:00
if ( state - > block_size > 16 * KIBI ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too big 'blocksize' specification in '%s' at line %u \n " , path , line ) ;
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
}
/* check if it's a power of 2 */
if ( ( state - > block_size & ( state - > block_size - 1 ) ) ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Not power of 2 'blocksize' specification in '%s' at line %u \n " , path , line ) ;
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
}
2016-01-16 07:27:28 +01:00
state - > block_size * = KIBI ;
2017-01-09 18:51:05 +01:00
} else if ( strcmp ( tag , " hashsize " ) = = 0
2020-05-02 14:21:40 +02:00
| | strcmp ( tag , " hash_size " ) = = 0 /* v11.0 used incorrectly this one, kept now for backward compatibility */
2017-01-09 18:51:05 +01:00
) {
2016-04-07 22:37:11 +02:00
uint32_t hash_size ;
ret = sgetu32 ( f , & hash_size ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'hashsize' specification in '%s' at line %u \n " , path , line ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-09-08 13:31:02 +02:00
if ( hash_size = = 2 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Unsupported 'hashsize 2' specification in '%s' at line %u! The minimum supported hash size is 4. \n " , path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( hash_size < 4 ) {
2016-04-07 22:37:11 +02:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too small 'hashsize' specification in '%s' at line %u \n " , path , line ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( hash_size > HASH_MAX ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too big 'hashsize' specification in '%s' at line %u \n " , path , line ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* check if it's a power of 2 */
if ( ( hash_size & ( hash_size - 1 ) ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Not power of 2 'hashsize' specification in '%s' at line %u \n " , path , line ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
BLOCK_HASH_SIZE = hash_size ;
2015-04-08 22:56:44 +02:00
} else if ( lev_config_scan ( tag , & level , & state - > raid_mode ) = = 0 ) {
2011-09-20 22:35:15 +02:00
char device [ PATH_MAX ] ;
2017-11-03 23:46:45 +01:00
char * split_map [ SPLIT_MAX + 1 ] ;
2016-04-26 21:48:39 +02:00
unsigned split_mac ;
2015-10-16 23:17:38 +02:00
uint64_t dev ;
int skip_access ;
2011-06-23 20:28:52 +02:00
2016-04-26 21:48:39 +02:00
if ( state - > parity [ level ] . split_mac ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Multiple '%s' specification in '%s' at line %u \n " , tag , path , line ) ;
2011-04-23 21:38:26 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-04-23 21:38:26 +02:00
}
2011-06-23 20:28:52 +02:00
2011-07-06 19:38:05 +02:00
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid '%s' specification in '%s' at line %u \n " , tag , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2013-01-15 22:32:20 +01:00
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty '%s' specification in '%s' at line %u \n " , tag , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
2017-11-03 23:46:45 +01:00
split_mac = strsplit ( split_map , SPLIT_MAX + 1 , buffer , " , " ) ;
2014-03-10 21:17:10 +01:00
2016-04-26 21:48:39 +02:00
if ( split_mac > SPLIT_MAX ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too many files in '%s' specification in '%s' at line %u \n " , tag , path , line ) ;
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-09-11 23:30:32 +02:00
2016-04-26 21:48:39 +02:00
skip_access = 0 ;
state - > parity [ level ] . split_mac = split_mac ;
for ( s = 0 ; s < split_mac ; + + s ) {
2025-12-22 00:03:38 +01:00
char uuid [ UUID_MAX ] ;
2016-04-26 21:48:39 +02:00
pathimport ( state - > parity [ level ] . split_map [ s ] . path , sizeof ( state - > parity [ level ] . split_map [ s ] . path ) , split_map [ s ] ) ;
if ( ! state - > opt . skip_parity_access ) {
struct stat st ;
/* get the device of the directory containing the parity file */
pathimport ( device , sizeof ( device ) , split_map [ s ] ) ;
2026-04-22 19:58:08 +02:00
/* use only the dir, as the parity file may not exist yet */
pathcut ( device ) ;
2016-04-26 21:48:39 +02:00
if ( stat ( device , & st ) = = 0 ) {
dev = st . st_dev ;
2026-01-05 15:55:38 +01:00
2025-12-22 00:03:38 +01:00
/* read the uuid, if unsupported use an empty one */
2026-04-22 19:58:08 +02:00
if ( devuuid ( dev , device , uuid , sizeof ( uuid ) ) ! = 0 ) {
2025-12-22 00:03:38 +01:00
* uuid = 0 ;
}
2015-10-16 23:17:38 +02:00
} else {
2016-04-26 21:48:39 +02:00
/* if the disk can be skipped */
if ( state - > opt . force_device ) {
/* use a fake device, and mark the disk to be skipped */
dev = 0 ;
2025-12-22 00:03:38 +01:00
* uuid = 0 ;
2016-04-26 21:48:39 +02:00
skip_access = 1 ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " DANGER! Skipping inaccessible parity disk '%s'... \n " , tag ) ;
2016-04-26 21:48:39 +02:00
} else {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing 'parity' dir '%s' specification in '%s' at line %u \n " , device , path , line ) ;
2015-10-16 23:17:38 +02:00
2016-04-26 21:48:39 +02:00
/* in "fix" we allow to continue anyway */
if ( strcmp ( state - > command , " fix " ) = = 0 ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " You can '%s' anyway, using 'snapraid --force-device %s'. \n " , state - > command , state - > command ) ;
2016-04-26 21:48:39 +02:00
}
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
2015-10-16 23:17:38 +02:00
}
}
2016-04-26 21:48:39 +02:00
} else {
/* use a fake device */
dev = 0 ;
2025-12-22 00:03:38 +01:00
* uuid = 0 ;
2015-10-16 23:17:38 +02:00
}
2016-04-26 21:48:39 +02:00
state - > parity [ level ] . split_map [ s ] . device = dev ;
2025-12-22 00:03:38 +01:00
pathcpy ( state - > parity [ level ] . split_map [ s ] . uuid , sizeof ( state - > parity [ level ] . split_map [ s ] . uuid ) , uuid ) ;
2014-03-10 21:17:10 +01:00
}
2013-09-11 23:30:32 +02:00
2016-04-26 21:48:39 +02:00
/* store the global parity skip_access */
2015-10-16 23:17:38 +02:00
state - > parity [ level ] . skip_access = skip_access ;
2013-09-15 21:12:41 +02:00
/* adjust the level */
2014-03-10 21:59:25 +01:00
if ( state - > level < level + 1 )
state - > level = level + 1 ;
2013-12-18 19:26:57 +01:00
} else if ( strcmp ( tag , " share " ) = = 0 ) {
if ( * state - > share ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Multiple 'share' specification in '%s' at line %u \n " , path , line ) ;
2013-12-18 19:26:57 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-12-18 19:26:57 +01:00
}
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'share' specification in '%s' at line %u \n " , path , line ) ;
2013-12-18 19:26:57 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-12-18 19:26:57 +01:00
}
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'share' specification in '%s' at line %u \n " , path , line ) ;
2013-12-18 19:26:57 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-12-18 19:26:57 +01:00
}
pathimport ( state - > share , sizeof ( state - > share ) , buffer ) ;
2013-02-22 22:17:54 +01:00
} else if ( strcmp ( tag , " pool " ) = = 0 ) {
struct stat st ;
if ( * state - > pool ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Multiple 'pool' specification in '%s' at line %u \n " , path , line ) ;
2013-02-22 22:17:54 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-22 22:17:54 +01:00
}
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'pool' specification in '%s' at line %u \n " , path , line ) ;
2013-02-22 22:17:54 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-22 22:17:54 +01:00
}
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'pool' specification in '%s' at line %u \n " , path , line ) ;
2013-02-22 22:17:54 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-22 22:17:54 +01:00
}
pathimport ( state - > pool , sizeof ( state - > pool ) , buffer ) ;
/* get the device of the directory containing the pool tree */
2013-03-10 09:46:20 +01:00
if ( stat ( buffer , & st ) ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing 'pool' dir '%s' specification in '%s' at line %u \n " , buffer , path , line ) ;
2013-02-22 22:17:54 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-02-22 22:17:54 +01:00
}
state - > pool_device = st . st_dev ;
2011-03-04 20:59:21 +01:00
} else if ( strcmp ( tag , " content " ) = = 0 ) {
2011-06-23 20:28:52 +02:00
struct snapraid_content * content ;
2011-09-20 22:35:15 +02:00
char device [ PATH_MAX ] ;
char * slash ;
struct stat st ;
2015-10-16 23:17:38 +02:00
uint64_t dev ;
2011-06-23 20:28:52 +02:00
2011-07-06 19:38:05 +02:00
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'content' specification in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2012-01-31 21:53:26 +01:00
if ( pathcmp ( buffer , " /dev/null " ) = = 0 | | pathcmp ( buffer , " NUL " ) = = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " You cannot use the null device as 'content' specification in '%s' at line %u \n " , path , line ) ;
2011-12-14 21:08:17 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-12-14 21:08:17 +01:00
}
2013-01-15 22:32:20 +01:00
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'content' specification in '%s' at line %u \n " , path , line ) ;
2014-02-18 00:05:32 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* check if the content file is already specified */
2014-09-27 00:32:39 +02:00
for ( i = state - > contentlist ; i ! = 0 ; i = i - > next ) {
2014-02-18 00:05:32 +01:00
content = i - > data ;
if ( pathcmp ( content - > content , buffer ) = = 0 )
break ;
}
if ( i ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Duplicate 'content' specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
2011-09-20 22:35:15 +02:00
/* get the device of the directory containing the content file */
pathimport ( device , sizeof ( device ) , buffer ) ;
slash = strrchr ( device , ' / ' ) ;
if ( slash )
2026-05-20 00:20:52 +02:00
slash [ 1 ] = 0 ;
2011-09-20 22:35:15 +02:00
else
pathcpy ( device , sizeof ( device ) , " . " ) ;
2015-10-16 23:17:38 +02:00
if ( stat ( device , & st ) = = 0 ) {
dev = st . st_dev ;
} else {
if ( state - > opt . skip_content_access ) {
/* use a fake device */
dev = 0 ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " WARNING! Skipping inaccessible content file '%s'... \n " , buffer ) ;
2015-10-16 23:17:38 +02:00
} else {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing 'content' dir '%s' specification in '%s' at line %u \n " , device , path , line ) ;
2015-10-16 23:17:38 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2011-09-20 22:35:15 +02:00
}
2015-10-16 23:17:38 +02:00
/* set the lock file at the first accessible content file */
if ( state - > lockfile [ 0 ] = = 0 & & dev ! = 0 ) {
2014-02-22 08:46:11 +01:00
pathcpy ( state - > lockfile , sizeof ( state - > lockfile ) , buffer ) ;
pathcat ( state - > lockfile , sizeof ( state - > lockfile ) , " .lock " ) ;
}
2015-10-16 23:17:38 +02:00
content = content_alloc ( buffer , dev ) ;
2011-06-23 20:28:52 +02:00
2011-04-25 13:43:51 +02:00
tommy_list_insert_tail ( & state - > contentlist , & content - > node , content ) ;
2016-01-10 21:19:20 +01:00
} else if ( strcmp ( tag , " data " ) = = 0 | | strcmp ( tag , " disk " ) = = 0 ) {
/* "disk" is the deprecated name up to SnapRAID 9.x */
2011-06-23 20:28:52 +02:00
char dir [ PATH_MAX ] ;
2011-09-20 22:35:15 +02:00
char device [ PATH_MAX ] ;
2015-12-28 12:09:13 +01:00
char uuid [ UUID_MAX ] ;
2011-09-23 21:48:50 +02:00
struct snapraid_disk * disk ;
2014-11-09 19:14:29 +01:00
uint64_t dev ;
2015-10-16 23:17:38 +02:00
int skip_access ;
2011-06-23 20:28:52 +02:00
2011-06-26 11:10:51 +02:00
ret = sgettok ( f , buffer , sizeof ( buffer ) ) ;
2011-07-06 19:38:05 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'data' name specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'data' name specification in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-06-26 11:10:51 +02:00
sgetspace ( f ) ;
2011-06-23 20:28:52 +02:00
2011-07-06 19:38:05 +02:00
ret = sgetlasttok ( f , dir , sizeof ( dir ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'data' dir specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
if ( ! * dir ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'data' dir specification in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-09-20 22:35:15 +02:00
/* get the device of the dir */
pathimport ( device , sizeof ( device ) , dir ) ;
2013-03-28 18:37:31 +01:00
/* check if the disk name already exists */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2013-03-28 18:37:31 +01:00
disk = i - > data ;
if ( strcmp ( disk - > name , buffer ) = = 0 )
break ;
}
if ( i ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Duplicate 'data' name '%s' at line %u \n " , buffer , line ) ;
2013-03-28 18:37:31 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-03-28 18:37:31 +01:00
}
2015-10-16 23:17:38 +02:00
/* if the disk has to be present */
skip_access = 0 ;
2014-11-09 19:14:29 +01:00
if ( ! state - > opt . skip_disk_access ) {
struct stat st ;
2015-10-16 23:17:38 +02:00
if ( stat ( device , & st ) = = 0 ) {
dev = st . st_dev ;
2015-12-28 12:09:13 +01:00
/* read the uuid, if unsupported use an empty one */
2026-04-22 19:58:08 +02:00
if ( devuuid ( dev , device , uuid , sizeof ( uuid ) ) ! = 0 ) {
2015-12-28 12:09:13 +01:00
* uuid = 0 ;
}
/* fake a different UUID when testing */
if ( state - > opt . fake_uuid ) {
snprintf ( uuid , sizeof ( uuid ) , " fake-uuid-%d " , state - > opt . fake_uuid ) ;
- - state - > opt . fake_uuid ;
}
2015-10-16 23:17:38 +02:00
} else {
/* if the disk can be skipped */
if ( state - > opt . force_device ) {
/* use a fake device, and mark the disk to be skipped */
dev = 0 ;
2015-12-28 12:09:13 +01:00
* uuid = 0 ;
2015-10-16 23:17:38 +02:00
skip_access = 1 ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " DANGER! Skipping inaccessible data disk '%s'... \n " , buffer ) ;
2015-10-16 23:17:38 +02:00
} else {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing 'disk' '%s' specification in '%s' at line %u \n " , dir , device , line ) ;
2014-11-09 19:14:29 +01:00
2015-10-16 23:17:38 +02:00
/* in "fix" we allow to continue anyway */
if ( strcmp ( state - > command , " fix " ) = = 0 ) {
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " You can '%s' anyway, using 'snapraid --force-device %s'. \n " , state - > command , state - > command ) ;
2015-10-16 23:17:38 +02:00
}
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2014-11-09 19:14:29 +01:00
} else {
2015-10-16 23:17:38 +02:00
/* use a fake device */
2014-11-09 19:14:29 +01:00
dev = 0 ;
2015-12-28 12:09:13 +01:00
* uuid = 0 ;
2014-11-09 19:14:29 +01:00
}
2015-12-28 12:09:13 +01:00
disk = disk_alloc ( buffer , dir , dev , uuid , skip_access ) ;
2011-09-23 21:48:50 +02:00
tommy_list_insert_tail ( & state - > disklist , & disk - > node , disk ) ;
2026-03-11 13:53:42 +01:00
} else if ( strcmp ( tag , " extra " ) = = 0 ) {
char dir [ PATH_MAX ] ;
char device [ PATH_MAX ] ;
char uuid [ UUID_MAX ] ;
struct snapraid_extra * extra ;
uint64_t dev ;
ret = sgettok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Invalid 'extra' name specification in '%s' at line %u \n " , path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * buffer ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Empty 'extra' name specification in '%s' at line %u \n " , path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
sgetspace ( f ) ;
ret = sgetlasttok ( f , dir , sizeof ( dir ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Invalid 'extra' dir specification in '%s' at line %u \n " , path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * dir ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Empty 'extra' dir specification in '%s' at line %u \n " , path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* get the device of the dir */
pathimport ( device , sizeof ( device ) , dir ) ;
/* check if the disk name already exists */
for ( i = state - > extralist ; i ! = 0 ; i = i - > next ) {
extra = i - > data ;
if ( strcmp ( extra - > name , buffer ) = = 0 )
break ;
}
if ( i ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Duplicate 'extra' name '%s' at line %u \n " , buffer , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* if the disk has to be present */
if ( ! state - > opt . skip_disk_access ) {
struct stat st ;
if ( stat ( device , & st ) = = 0 ) {
dev = st . st_dev ;
/* read the uuid, if unsupported use an empty one */
2026-04-22 19:58:08 +02:00
if ( devuuid ( dev , device , uuid , sizeof ( uuid ) ) ! = 0 ) {
2026-03-11 13:53:42 +01:00
* uuid = 0 ;
}
/* fake a different UUID when testing */
if ( state - > opt . fake_uuid ) {
snprintf ( uuid , sizeof ( uuid ) , " fake-uuid-%d " , state - > opt . fake_uuid ) ;
- - state - > opt . fake_uuid ;
}
} else {
/* use a fake device, and mark the disk to be skipped */
dev = 0 ;
* uuid = 0 ;
}
} else {
/* use a fake device */
dev = 0 ;
* uuid = 0 ;
}
extra = extra_alloc ( buffer , dir , dev , uuid ) ;
tommy_list_insert_tail ( & state - > extralist , & extra - > node , extra ) ;
2015-04-08 22:56:44 +02:00
} else if ( strcmp ( tag , " smartctl " ) = = 0 ) {
char custom [ PATH_MAX ] ;
2026-06-15 14:41:03 +02:00
char parsed_smartctl [ SMART_MAX ] ;
char parsed_smartctl_info [ SMART_MAX ] ;
2015-04-08 22:56:44 +02:00
ret = sgettok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartctl' name in '%s' at line %u \n " , path , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * buffer ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'smartctl' name in '%s' at line %u \n " , path , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
sgetspace ( f ) ;
ret = sgetlasttok ( f , custom , sizeof ( custom ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartctl' option in '%s' at line %u \n " , path , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * custom ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'smartctl' option in '%s' at line %u \n " , path , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-06-15 14:41:03 +02:00
if ( parse_smartctl ( custom , parsed_smartctl , sizeof ( parsed_smartctl ) , parsed_smartctl_info , sizeof ( parsed_smartctl_info ) ) ! = 0 ) {
2015-04-10 18:29:06 +02:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartctl' option in '%s' at line %u \n " , path , line ) ;
2015-04-10 18:29:06 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-04-08 22:56:44 +02:00
/* search for parity */
if ( lev_config_scan ( buffer , & level , 0 ) = = 0 ) {
if ( state - > parity [ level ] . smartctl [ 0 ] ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Duplicate parity 'smartctl' '%s' at line %u \n " , buffer , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-06-15 14:41:03 +02:00
pathcpy ( state - > parity [ level ] . smartctl , sizeof ( state - > parity [ level ] . smartctl ) , parsed_smartctl ) ;
pathcpy ( state - > parity [ level ] . smartctl_info , sizeof ( state - > parity [ level ] . smartctl_info ) , parsed_smartctl_info ) ;
2015-04-08 22:56:44 +02:00
} else {
2015-12-20 17:23:58 +01:00
/* search the disk */
2026-03-11 13:53:42 +01:00
struct snapraid_disk * found_disk = 0 ;
2015-04-08 22:56:44 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2026-03-11 13:53:42 +01:00
struct snapraid_disk * disk = i - > data ;
if ( strcmp ( disk - > name , buffer ) = = 0 ) {
found_disk = disk ;
2015-04-08 22:56:44 +02:00
break ;
2026-03-11 13:53:42 +01:00
}
2015-04-08 22:56:44 +02:00
}
2026-03-11 13:53:42 +01:00
struct snapraid_extra * found_extra = 0 ;
for ( i = state - > extralist ; i ! = 0 ; i = i - > next ) {
struct snapraid_extra * extra = i - > data ;
if ( strcmp ( extra - > name , buffer ) = = 0 ) {
found_extra = extra ;
break ;
}
2015-04-08 22:56:44 +02:00
}
2026-03-11 13:53:42 +01:00
if ( found_disk ) {
if ( found_disk - > smartctl [ 0 ] ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Duplicate 'smartctl' name '%s' at line %u \n " , buffer , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-06-15 14:41:03 +02:00
pathcpy ( found_disk - > smartctl , sizeof ( found_disk - > smartctl ) , parsed_smartctl ) ;
pathcpy ( found_disk - > smartctl_info , sizeof ( found_disk - > smartctl_info ) , parsed_smartctl_info ) ;
2026-03-11 13:53:42 +01:00
} else if ( found_extra ) {
if ( found_extra - > smartctl [ 0 ] ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER , " Duplicate 'smartctl' name '%s' at line %u \n " , buffer , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-06-15 14:41:03 +02:00
pathcpy ( found_extra - > smartctl , sizeof ( found_extra - > smartctl ) , parsed_smartctl ) ;
pathcpy ( found_extra - > smartctl_info , sizeof ( found_extra - > smartctl_info ) , parsed_smartctl_info ) ;
2026-03-11 13:53:42 +01:00
} else {
2015-04-08 22:56:44 +02:00
/* LCOV_EXCL_START */
2026-03-11 13:53:42 +01:00
log_fatal ( EUSER , " Unknown 'smartctl' name '%s' at line %u \n " , buffer , line ) ;
2015-04-08 22:56:44 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2025-09-15 16:56:09 +02:00
} else if ( strcmp ( tag , " smartignore " ) = = 0 ) {
2026-06-25 22:52:53 +02:00
struct smartignore_struct * smart ;
2025-09-15 16:56:09 +02:00
ret = sgettok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartignore' name in '%s' at line %u \n " , path , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * buffer ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'smartignore' name in '%s' at line %u \n " , path , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
sgetspace ( f ) ;
if ( strcmp ( buffer , " * " ) = = 0 ) {
smart = state - > smartignore ;
2025-10-26 18:06:08 +01:00
} else if ( lev_config_scan ( buffer , & level , 0 ) = = 0 ) { /* search for parity */
2025-09-15 16:56:09 +02:00
smart = state - > parity [ level ] . smartignore ;
} else {
/* search the disk */
2026-03-11 13:53:42 +01:00
struct snapraid_disk * found_disk = 0 ;
2025-09-15 16:56:09 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2026-03-11 13:53:42 +01:00
struct snapraid_disk * disk = i - > data ;
if ( strcmp ( disk - > name , buffer ) = = 0 ) {
found_disk = disk ;
2025-09-15 16:56:09 +02:00
break ;
2026-03-11 13:53:42 +01:00
}
2025-09-15 16:56:09 +02:00
}
2026-03-11 13:53:42 +01:00
struct snapraid_extra * found_extra = 0 ;
for ( i = state - > extralist ; i ! = 0 ; i = i - > next ) {
struct snapraid_extra * extra = i - > data ;
if ( strcmp ( extra - > name , buffer ) = = 0 ) {
found_extra = extra ;
break ;
}
}
if ( found_disk ) {
smart = found_disk - > smartignore ;
} else if ( found_extra ) {
smart = found_extra - > smartignore ;
} else {
2025-09-15 16:56:09 +02:00
/* LCOV_EXCL_START */
2026-03-11 13:53:42 +01:00
log_fatal ( EUSER , " Unknown 'smartignore' name '%s' at line %u \n " , buffer , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
int si = 0 ;
while ( 1 ) {
char * e ;
ret = sgettok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartignore' specification in '%s' at line %u \n " , path , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( ! * buffer )
break ;
if ( si = = SMART_IGNORE_MAX ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Too many 'smartignore' specification in '%s' at line %u \n " , path , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-06-25 22:52:53 +02:00
int val = strtou ( buffer , & e , 0 ) ;
2026-05-21 14:47:03 +02:00
2026-06-25 22:52:53 +02:00
if ( e & & ! * e ) {
/* numerical form */
if ( val < 1 | | val > = 256 ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER ,
" Invalid 'smartignore' specification in '%s' at line %u. It should be between 1 and 255. \n " ,
path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
smart [ si ] . attr_index = val ;
smart [ si ] . attr_name [ 0 ] = 0 ;
} else {
/* name form */
if ( strlen ( buffer ) > = SMART_MAX ) {
/* LCOV_EXCL_START */
log_fatal ( EUSER ,
" Invalid 'smartignore' specification in '%s' at line %u. Attribute name too long. \n " ,
path , line ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
smart [ si ] . attr_index = 0 ;
pathcpy ( smart [ si ] . attr_name , sizeof ( smart [ si ] . attr_name ) , buffer ) ;
2025-09-15 16:56:09 +02:00
}
+ + si ;
sgetspace ( f ) ;
}
if ( si = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'smartignore' specification in '%s' at line %u \n " , path , line ) ;
2025-09-15 16:56:09 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2025-09-21 19:54:10 +02:00
} else if ( strcmp ( tag , " temp_limit " ) = = 0 ) {
unsigned temp ;
ret = sgetu32 ( f , & temp ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'temp_limit' specification in '%s' at line %u \n " , path , line ) ;
2025-09-21 19:54:10 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( temp < 40 | | temp > 70 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'temp_limit' temperature specification in '%s' at line %u. It must be between 40 and 70 \n " , path , line ) ;
2025-09-21 19:54:10 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
state - > thermal_temperature_limit = temp ;
} else if ( strcmp ( tag , " temp_sleep " ) = = 0 ) {
unsigned time ;
ret = sgetu32 ( f , & time ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'temp_sleep' specification in '%s' at line %u \n " , path , line ) ;
2025-09-21 19:54:10 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2025-10-17 09:12:21 +02:00
if ( time < 3 | | time > 120 ) {
2025-09-21 19:54:10 +02:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'temp_sleep' temperature specification in '%s' at line %u. It must be between 3 and 120 \n " , path , line ) ;
2025-09-21 19:54:10 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
state - > thermal_cooldown_time = time * 60 ;
2011-12-08 22:40:30 +01:00
} else if ( strcmp ( tag , " nohidden " ) = = 0 ) {
state - > filter_hidden = 1 ;
2026-04-21 15:19:25 +02:00
} else if ( strcmp ( tag , " snapshot " ) = = 0 ) {
state - > snapshot = 1 ;
2011-03-16 23:52:10 +01:00
} else if ( strcmp ( tag , " exclude " ) = = 0 ) {
2011-06-23 20:28:52 +02:00
struct snapraid_filter * filter ;
2011-07-06 19:38:05 +02:00
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'exclude' specification in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2013-01-15 22:32:20 +01:00
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'exclude' specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
2026-01-10 16:30:35 +01:00
filter = filter_alloc_file ( - 1 , " " , buffer ) ;
2011-03-16 23:52:10 +01:00
if ( ! filter ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'exclude' specification '%s' in '%s' at line %u \n " , buffer , path , line ) ;
2011-03-16 23:52:10 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-16 23:52:10 +01:00
}
2011-04-26 18:51:42 +02:00
tommy_list_insert_tail ( & state - > filterlist , & filter - > node , filter ) ;
} else if ( strcmp ( tag , " include " ) = = 0 ) {
2011-06-23 20:28:52 +02:00
struct snapraid_filter * filter ;
2011-07-06 19:38:05 +02:00
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'include' specification in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2013-01-15 22:32:20 +01:00
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'include' specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
2026-01-10 16:30:35 +01:00
filter = filter_alloc_file ( 1 , " " , buffer ) ;
2011-04-26 18:51:42 +02:00
if ( ! filter ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'include' specification '%s' in '%s' at line %u \n " , buffer , path , line ) ;
2011-04-26 18:51:42 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-04-26 18:51:42 +02:00
}
tommy_list_insert_tail ( & state - > filterlist , & filter - > node , filter ) ;
2012-03-13 23:59:59 +01:00
} else if ( strcmp ( tag , " autosave " ) = = 0 ) {
char * e ;
ret = sgetlasttok ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'autosave' specification in '%s' at line %u \n " , path , line ) ;
2012-03-13 23:59:59 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2012-03-13 23:59:59 +01:00
}
2013-01-15 22:32:20 +01:00
if ( ! * buffer ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Empty 'autosave' specification in '%s' at line %u \n " , path , line ) ;
2013-01-15 22:32:20 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-01-15 22:32:20 +01:00
}
2012-03-13 23:59:59 +01:00
state - > autosave = strtoul ( buffer , & e , 0 ) ;
if ( ! e | | * e ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid 'autosave' specification in '%s' at line %u \n " , path , line ) ;
2012-03-13 23:59:59 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2012-03-13 23:59:59 +01:00
}
2015-03-29 23:18:30 +02:00
/* convert to GB */
state - > autosave * = GIGA ;
2011-06-23 20:28:52 +02:00
} else if ( tag [ 0 ] = = 0 ) {
/* allow empty lines */
} else if ( tag [ 0 ] = = ' # ' ) {
2011-06-26 11:10:51 +02:00
ret = sgetline ( f , buffer , sizeof ( buffer ) ) ;
2011-07-06 19:38:05 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid comment in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
2011-03-04 20:59:21 +01:00
} else {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Invalid command '%s' in '%s' at line %u \n " , tag , path , line ) ;
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
}
2011-06-23 20:28:52 +02:00
/* skip final spaces */
2011-06-26 11:10:51 +02:00
sgetspace ( f ) ;
2011-06-23 20:28:52 +02:00
/* next line */
2011-06-26 11:10:51 +02:00
c = sgeteol ( f ) ;
2011-06-23 20:28:52 +02:00
if ( c = = EOF ) {
break ;
}
if ( c ! = ' \n ' ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Extra data in '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-06-23 20:28:52 +02:00
}
+ + line ;
}
2011-06-26 11:10:51 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reading the configuration file '%s' at line %u \n " , path , line ) ;
2011-06-23 20:28:52 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-03-04 20:59:21 +01:00
}
2011-06-26 11:10:51 +02:00
sclose ( f ) ;
2011-03-04 20:59:21 +01:00
2013-10-30 21:45:53 +01:00
state_config_check ( state , path , filterlist_disk ) ;
2011-09-18 22:43:41 +02:00
2013-06-15 23:42:11 +02:00
/* select the default hash */
2013-06-29 18:11:36 +02:00
if ( state - > opt . force_murmur3 ) {
2013-06-15 23:42:11 +02:00
state - > besthash = HASH_MURMUR3 ;
2013-06-29 18:11:36 +02:00
} else if ( state - > opt . force_spooky2 ) {
2013-06-15 23:42:11 +02:00
state - > besthash = HASH_SPOOKY2 ;
} else {
2026-01-08 18:47:53 +01:00
state - > besthash = membesthash ( ) ;
2013-06-15 23:42:11 +02:00
}
/* by default use the best hash */
state - > hash = state - > besthash ;
2013-06-16 00:33:46 +02:00
/* by default use a random hash seed */
2026-06-11 18:36:13 +02:00
if ( os_randomize ( state - > hashseed , HASH_MAX ) ! = 0 ) {
2014-12-27 11:56:55 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Failed to retrieve random values. \n " ) ;
2014-12-27 11:56:55 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-06-16 00:33:46 +02:00
2013-07-01 23:08:25 +02:00
/* no previous hash by default */
state - > prevhash = HASH_UNDEFINED ;
2013-09-26 20:34:01 +02:00
/* intentionally not set the prevhashseed, if used valgrind will warn about it */
2013-07-01 23:08:25 +02:00
2015-04-20 22:07:42 +02:00
log_tag ( " blocksize:%u \n " , state - > block_size ) ;
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2013-10-03 22:44:33 +02:00
struct snapraid_disk * disk = i - > data ;
2026-04-21 15:19:25 +02:00
log_tag ( " data:%s:%s:%s \n " , esc_tag ( disk - > name ) , esc_tag ( disk - > mount_point ) , disk - > uuid ) ;
2013-10-03 22:44:33 +02:00
}
2026-03-11 13:53:42 +01:00
for ( i = state - > extralist ; i ! = 0 ; i = i - > next ) {
struct snapraid_extra * extra = i - > data ;
2026-05-29 11:01:44 +02:00
log_tag ( " extra:%s:%s:%s \n " , esc_tag ( extra - > name ) , esc_tag ( extra - > dir ) , extra - > uuid ) ;
2026-03-11 13:53:42 +01:00
}
2013-09-15 21:12:41 +02:00
2015-04-20 22:07:42 +02:00
log_tag ( " mode:%s \n " , lev_raid_name ( state - > raid_mode , state - > level ) ) ;
2025-12-22 00:03:38 +01:00
for ( l = 0 ; l < state - > level ; + + l ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s:%s:%s \n " , lev_config_name ( l ) , esc_tag ( state - > parity [ l ] . split_map [ 0 ] . path ) , state - > parity [ l ] . split_map [ 0 ] . uuid ) ;
2025-12-22 00:03:38 +01:00
for ( s = 1 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2026-05-29 11:01:44 +02:00
log_tag ( " %s/%u:%s:%s \n " , lev_config_name ( l ) , s , esc_tag ( state - > parity [ l ] . split_map [ s ] . path ) , state - > parity [ l ] . split_map [ s ] . uuid ) ;
2025-12-22 00:03:38 +01:00
}
}
2013-10-03 22:44:33 +02:00
if ( state - > pool [ 0 ] ! = 0 )
2026-05-29 11:01:44 +02:00
log_tag ( " pool:%s \n " , esc_tag ( state - > pool ) ) ;
2015-01-27 23:32:51 +01:00
if ( state - > share [ 0 ] ! = 0 )
2026-05-29 11:01:44 +02:00
log_tag ( " share:%s \n " , esc_tag ( state - > share ) ) ;
2015-01-27 23:32:51 +01:00
if ( state - > autosave ! = 0 )
2015-04-20 22:07:42 +02:00
log_tag ( " autosave:% " PRIu64 " \n " , state - > autosave ) ;
2015-01-27 23:32:51 +01:00
for ( i = tommy_list_head ( & state - > filterlist ) ; i ! = 0 ; i = i - > next ) {
char out [ PATH_MAX ] ;
struct snapraid_filter * filter = i - > data ;
2026-05-29 11:01:44 +02:00
log_tag ( " filter:%s \n " , esc_tag ( filter_type ( filter , out , sizeof ( out ) ) ) ) ;
2015-01-27 23:32:51 +01:00
}
if ( state - > filter_hidden )
2015-04-20 22:07:42 +02:00
log_tag ( " filter:nohidden: \n " ) ;
log_flush ( ) ;
2011-10-01 19:18:32 +02:00
}
2011-09-20 22:35:15 +02:00
2014-11-09 10:51:48 +01:00
/**
2015-10-07 22:07:20 +02:00
* Find a disk by name .
2014-11-09 10:51:48 +01:00
*/
2015-12-28 13:55:22 +01:00
static struct snapraid_disk * find_disk_by_name ( struct snapraid_state * state , const char * name )
2014-11-09 10:51:48 +01:00
{
tommy_node * i ;
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
if ( strcmp ( disk - > name , name ) = = 0 )
return disk ;
}
if ( state - > no_conf ) {
/* without a configuration file, add disks automatically */
struct snapraid_disk * disk ;
2015-12-28 12:09:13 +01:00
disk = disk_alloc ( name , " DUMMY/ " , - 1 , " " , 0 ) ;
2014-11-09 10:51:48 +01:00
tommy_list_insert_tail ( & state - > disklist , & disk - > node , disk ) ;
return disk ;
}
return 0 ;
}
2015-12-28 13:55:22 +01:00
/**
* Find a disk by UUID .
*/
static struct snapraid_disk * find_disk_by_uuid ( struct snapraid_state * state , const char * uuid )
{
tommy_node * i ;
struct snapraid_disk * found = 0 ;
2026-04-27 15:43:06 +02:00
/*
* Special test case to find the first matching UUID
* when testing UUID are all equal or not supported
* and we should handle this case specifically
*/
2015-12-28 13:55:22 +01:00
if ( state - > opt . match_first_uuid )
return state - > disklist - > data ;
/* LCOV_EXCL_START */
/* never find an empty uuid */
if ( ! * uuid )
return 0 ;
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
if ( strcmp ( disk - > uuid , uuid ) = = 0 ) {
/* never match duplicate UUID */
if ( found )
return 0 ;
found = disk ;
}
}
return found ;
/* LCOV_EXCL_STOP */
}
2011-10-01 19:18:32 +02:00
/**
2015-10-07 22:07:20 +02:00
* Update the disk mapping if required .
2011-10-01 19:18:32 +02:00
*/
static void state_map ( struct snapraid_state * state )
{
unsigned hole ;
tommy_node * i ;
2013-03-03 22:39:46 +01:00
unsigned uuid_mismatch ;
2026-06-04 00:34:51 +02:00
size_t diskcount ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2011-09-20 22:35:15 +02:00
2026-04-27 15:43:06 +02:00
/*
* Remove all the mapping without a disk
* this happens when a disk is removed from the configuration file
* From SnapRAID 4.0 mappings are automatically removed if a disk is not used
* when saving the content file , but we keep this code to import older content files .
*/
2014-09-27 00:32:39 +02:00
for ( i = state - > maplist ; i ! = 0 ; ) {
2011-10-01 19:18:32 +02:00
struct snapraid_map * map = i - > data ;
struct snapraid_disk * disk ;
2011-09-20 22:35:15 +02:00
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , map - > name ) ;
2011-09-20 22:35:15 +02:00
2011-10-01 19:18:32 +02:00
/* go to the next mapping before removing */
i = i - > next ;
2014-09-27 00:32:39 +02:00
2014-11-09 10:51:48 +01:00
if ( disk = = 0 ) {
2011-10-01 19:18:32 +02:00
/* disk not found, remove the mapping */
tommy_list_remove_existing ( & state - > maplist , & map - > node ) ;
map_free ( map ) ;
2011-09-20 22:35:15 +02:00
}
2011-09-18 22:43:41 +02:00
}
2011-09-20 22:35:15 +02:00
2026-04-27 15:43:06 +02:00
/*
* Maps each unmapped disk present in the configuration file in the first available hole
* this happens when you add disks for the first time in the configuration file
*/
2011-10-01 19:18:32 +02:00
hole = 0 ; /* first position to try */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2011-10-01 19:18:32 +02:00
struct snapraid_disk * disk = i - > data ;
struct snapraid_map * map ;
tommy_node * j ;
2011-09-23 21:48:50 +02:00
2011-10-01 19:18:32 +02:00
/* check if the disk is already mapped */
2014-09-27 00:32:39 +02:00
for ( j = state - > maplist ; j ! = 0 ; j = j - > next ) {
2011-10-01 19:18:32 +02:00
map = j - > data ;
if ( strcmp ( disk - > name , map - > name ) = = 0 ) {
/* mapping found */
break ;
}
}
2014-11-09 19:14:29 +01:00
if ( j ! = 0 ) {
/* mapping is present, then copy the free blocks into to disk */
disk - > total_blocks = map - > total_blocks ;
disk - > free_blocks = map - > free_blocks ;
2011-10-01 19:18:32 +02:00
continue ;
2014-11-09 19:14:29 +01:00
}
2011-10-01 19:18:32 +02:00
/* mapping not found, search for an hole */
while ( 1 ) {
2014-09-27 00:32:39 +02:00
for ( j = state - > maplist ; j ! = 0 ; j = j - > next ) {
2011-10-01 19:18:32 +02:00
map = j - > data ;
if ( map - > position = = hole ) {
/* position already used */
break ;
2011-09-20 22:35:15 +02:00
}
}
2014-09-27 00:32:39 +02:00
if ( j = = 0 ) {
2011-10-01 19:18:32 +02:00
/* hole found */
break ;
2011-09-20 22:35:15 +02:00
}
2011-10-01 19:18:32 +02:00
/* try with the next one */
+ + hole ;
2011-09-20 22:35:15 +02:00
}
2011-10-01 19:18:32 +02:00
/* insert the new mapping */
2014-11-09 19:14:29 +01:00
map = map_alloc ( disk - > name , hole , 0 , 0 , " " ) ;
2011-10-01 19:18:32 +02:00
tommy_list_insert_tail ( & state - > maplist , & map - > node , map ) ;
2011-03-04 20:59:21 +01:00
}
2013-03-03 22:39:46 +01:00
2013-12-14 17:54:04 +01:00
/* without configuration don't check for number of data disks or uuid changes */
2013-11-30 18:00:49 +01:00
if ( state - > no_conf )
return ;
2014-11-09 19:51:58 +01:00
/* counter for the number of UUID mismatches */
2013-03-03 22:39:46 +01:00
uuid_mismatch = 0 ;
2015-10-07 22:07:20 +02:00
/* check if mapping match the disk uuid */
2014-11-09 19:14:29 +01:00
if ( ! state - > opt . skip_disk_access ) {
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
struct snapraid_map * map = i - > data ;
struct snapraid_disk * disk ;
2013-03-03 22:39:46 +01:00
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , map - > name ) ;
2014-11-09 19:14:29 +01:00
if ( disk = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Mapping not found for '%s' \n " , map - > name ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2013-03-03 22:39:46 +01:00
2026-06-28 23:35:44 +02:00
if ( disk - > uuid [ 0 ] = = 0 ) {
2015-12-28 12:09:13 +01:00
/* if uuid is not available, skip this one */
2014-11-09 19:14:29 +01:00
continue ;
}
2013-12-14 17:54:04 +01:00
2014-11-09 19:14:29 +01:00
/* if the uuid is changed */
2026-06-28 23:35:44 +02:00
if ( strcmp ( disk - > uuid , map - > content_uuid ) ! = 0 ) {
2014-11-09 19:14:29 +01:00
/* mark the disk as with an UUID change */
disk - > has_different_uuid = 1 ;
/* if the previous uuid is available */
2026-06-28 23:35:44 +02:00
if ( map - > content_uuid [ 0 ] ! = 0 ) {
2014-11-09 19:14:29 +01:00
/* count the number of uuid change */
+ + uuid_mismatch ;
2026-06-28 23:35:44 +02:00
log_error ( ESOFT , " UUID change for disk '%s' from '%s' to '%s' \n " , disk - > name , map - > content_uuid , disk - > uuid ) ;
2014-11-09 19:14:29 +01:00
} else {
2026-04-27 15:43:06 +02:00
/*
* No message here , because having a disk without
* UUID is the normal state of an empty disk
*/
2014-11-09 19:14:29 +01:00
disk - > had_empty_uuid = 1 ;
}
/* write the new state with the new uuid */
state - > need_write = 1 ;
2013-03-03 22:39:46 +01:00
}
2014-11-09 19:14:29 +01:00
}
}
2013-03-03 22:39:46 +01:00
2015-10-07 22:07:20 +02:00
/* check the parity uuid */
2014-11-09 19:14:29 +01:00
if ( ! state - > opt . skip_parity_access ) {
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2026-06-28 22:47:58 +02:00
if ( state - > parity [ l ] . split_map [ s ] . uuid [ 0 ] = = 0 ) {
2026-06-28 23:35:44 +02:00
/* if uuid is not available, skip this one */
2016-04-26 21:48:39 +02:00
continue ;
2014-11-09 19:51:58 +01:00
}
2016-04-26 21:48:39 +02:00
/* if the uuid is changed */
2026-06-28 22:47:58 +02:00
if ( strcmp ( state - > parity [ l ] . split_map [ s ] . uuid , state - > parity [ l ] . split_map [ s ] . content_uuid ) ! = 0 ) {
2016-04-26 21:48:39 +02:00
/* if the previous uuid is available */
2026-06-28 22:47:58 +02:00
if ( state - > parity [ l ] . split_map [ s ] . content_uuid [ 0 ] ! = 0 ) {
2016-04-26 21:48:39 +02:00
/* count the number of uuid change */
+ + uuid_mismatch ;
2026-06-28 22:47:58 +02:00
log_error ( ESOFT , " UUID change for parity '%s/%u' from '%s' to '%s' \n " , lev_config_name ( l ) , s , state - > parity [ l ] . split_map [ s ] . content_uuid , state - > parity [ l ] . split_map [ s ] . uuid ) ;
2016-04-26 21:48:39 +02:00
}
/* write the new state with the new uuid */
state - > need_write = 1 ;
}
2014-11-09 19:51:58 +01:00
}
2013-03-03 22:39:46 +01:00
}
}
2013-06-29 18:11:36 +02:00
if ( ! state - > opt . force_uuid & & uuid_mismatch > state - > level ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Too many disks have changed UUIDs since the last 'sync'. \n " ) ;
log_fatal ( ESOFT , " If this happened because you actually replaced them, \n " ) ;
log_fatal ( ESOFT , " you can still '%s', using 'snapraid --force-uuid %s'. \n " , state - > command , state - > command ) ;
log_fatal ( ESOFT , " Alternatively, you may have misconfigured the disk mount points, \n " ) ;
log_fatal ( ESOFT , " and you need to restore the mount points to the state of the latest sync. \n " ) ;
2013-03-03 22:39:46 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-03-03 22:39:46 +01:00
}
2013-10-25 18:03:05 +02:00
/* count the number of data disks, including holes left after removing some */
diskcount = 0 ;
2014-09-27 00:32:39 +02:00
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
2013-10-25 18:03:05 +02:00
struct snapraid_map * map = i - > data ;
if ( map - > position + 1 > diskcount )
diskcount = map - > position + 1 ;
}
2015-02-02 21:17:15 +01:00
/* ensure to don't go over the limit of the RAID engine */
2013-10-28 22:55:32 +01:00
if ( diskcount > RAID_DATA_MAX ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ESOFT , " Too many data disks. Maximum allowed is %u. \n " , RAID_DATA_MAX ) ;
2013-10-28 22:55:32 +01:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-10-28 22:55:32 +01:00
}
2013-11-05 19:40:32 +01:00
2015-02-02 21:17:15 +01:00
/* now count the real number of data disks, excluding holes left after removing some */
diskcount = tommy_list_count ( & state - > maplist ) ;
2013-11-05 19:40:32 +01:00
/* recommend number of parities */
2015-11-24 18:49:20 +01:00
if ( ! state - > opt . no_warnings ) {
2026-02-28 10:08:09 +01:00
/* LCOV_EXCL_START */
2015-11-24 18:49:20 +01:00
if ( diskcount > = 36 & & state - > level < 6 ) {
2026-06-04 00:34:51 +02:00
log_info ( EUSER , " WARNING! For %zu disks, it's recommended to use six parity levels. \n " , diskcount ) ;
2015-11-24 18:49:20 +01:00
} else if ( diskcount > = 29 & & state - > level < 5 ) {
2026-06-04 00:34:51 +02:00
log_info ( EUSER , " WARNING! For %zu disks, it's recommended to use five parity levels. \n " , diskcount ) ;
2015-11-24 18:49:20 +01:00
} else if ( diskcount > = 22 & & state - > level < 4 ) {
2026-06-04 00:34:51 +02:00
log_info ( EUSER , " WARNING! For %zu disks, it's recommended to use four parity levels. \n " , diskcount ) ;
2015-11-24 18:49:20 +01:00
} else if ( diskcount > = 15 & & state - > level < 3 ) {
2026-06-04 00:34:51 +02:00
log_info ( EUSER , " WARNING! For %zu disks, it's recommended to use three parity levels. \n " , diskcount ) ;
2015-11-24 18:49:20 +01:00
} else if ( diskcount > = 5 & & state - > level < 2 ) {
2026-06-04 00:34:51 +02:00
log_info ( EUSER , " WARNING! For %zu disks, it's recommended to use two parity levels. \n " , diskcount ) ;
2015-11-24 18:49:20 +01:00
}
2026-02-28 10:08:09 +01:00
/* LCOV_EXCL_STOP */
2013-11-05 19:40:32 +01:00
}
2011-10-01 19:18:32 +02:00
}
2011-06-20 20:35:04 +02:00
2014-11-09 19:14:29 +01:00
void state_refresh ( struct snapraid_state * state )
{
tommy_node * i ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2026-01-30 09:54:54 +01:00
uint64_t bs = ( uint64_t ) state - > block_size ;
2014-11-09 19:14:29 +01:00
/* for all disks */
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
struct snapraid_map * map = i - > data ;
struct snapraid_disk * disk ;
uint64_t total_space ;
uint64_t free_space ;
int ret ;
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , map - > name ) ;
2014-11-09 19:14:29 +01:00
if ( disk = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Mapping not found for '%s' \n " , map - > name ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2026-05-20 00:17:18 +02:00
if ( disk - > skip_access )
continue ;
2026-04-21 15:19:25 +02:00
ret = fsinfo ( disk - > mount_point , 0 , 0 , & total_space , & free_space , disk - > fstype , sizeof ( disk - > fstype ) , disk - > fslabel , sizeof ( disk - > fslabel ) ) ;
2014-11-09 19:14:29 +01:00
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-04-21 15:19:25 +02:00
log_fatal ( errno , " Error accessing disk '%s' to retrieve file-system info. %s. \n " , disk - > mount_point , strerror ( errno ) ) ;
2014-11-09 19:14:29 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* set the new free blocks */
2026-01-30 09:54:54 +01:00
map - > total_blocks = total_space / bs ;
map - > free_blocks = free_space / bs ;
2014-11-09 19:14:29 +01:00
/* also update the disk info */
disk - > total_blocks = map - > total_blocks ;
disk - > free_blocks = map - > free_blocks ;
2026-01-29 18:43:00 +01:00
2026-05-29 11:01:44 +02:00
log_tag ( " fsinfo_data_split:%s:% " PRIu64 " :% " PRIu64 " :%s:%s \n " , esc_tag ( disk - > name ) , disk - > total_blocks * bs , disk - > free_blocks * bs , disk - > fstype , disk - > fslabel ) ;
log_tag ( " fsinfo_data:%s:% " PRIu64 " :% " PRIu64 " \n " , esc_tag ( disk - > name ) , disk - > total_blocks * bs , disk - > free_blocks * bs ) ;
2014-11-09 19:14:29 +01:00
}
/* for all parities */
for ( l = 0 ; l < state - > level ; + + l ) {
2016-04-26 21:48:39 +02:00
/* set the new free blocks */
state - > parity [ l ] . total_blocks = 0 ;
state - > parity [ l ] . free_blocks = 0 ;
2014-11-09 19:14:29 +01:00
2026-05-20 00:17:18 +02:00
if ( state - > parity [ l ] . skip_access )
continue ;
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2026-01-29 18:43:00 +01:00
struct snapraid_split * split = & state - > parity [ l ] . split_map [ s ] ;
2016-04-26 21:48:39 +02:00
uint64_t total_space ;
uint64_t free_space ;
int ret ;
2014-11-09 19:14:29 +01:00
2026-01-29 18:43:00 +01:00
ret = fsinfo ( split - > path , 0 , 0 , & total_space , & free_space , split - > fstype , sizeof ( split - > fstype ) , split - > fslabel , sizeof ( split - > fslabel ) ) ;
2016-04-26 21:48:39 +02:00
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error accessing file '%s' to retrieve file-system info. %s. \n " , split - > path , strerror ( errno ) ) ;
2016-04-26 21:48:39 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-01-30 09:54:54 +01:00
# ifdef WIN32
/*
* Take into account the space we have to leave free
* to avoid the waring about low space
*/
if ( free_space > = WINDOWS_SPACEHOLDER_SIZE )
free_space - = WINDOWS_SPACEHOLDER_SIZE ;
else
free_space = 0 ;
# endif
2016-04-26 21:48:39 +02:00
/* add the new free blocks */
2026-01-30 09:54:54 +01:00
uint64_t split_total_blocks = total_space / bs ;
uint64_t split_free_blocks = free_space / bs ;
2026-01-29 18:43:00 +01:00
2026-01-30 09:54:54 +01:00
state - > parity [ l ] . total_blocks + = split_total_blocks ;
state - > parity [ l ] . free_blocks + = split_free_blocks ;
if ( s = = 0 )
log_tag ( " fsinfo_parity_split:%s:% " PRIu64 " :% " PRIu64 " :%s:%s \n " , lev_config_name ( l ) , split_total_blocks * bs , split_free_blocks * bs , split - > fstype , split - > fslabel ) ;
else
log_tag ( " fsinfo_parity_split:%s/%u:% " PRIu64 " :% " PRIu64 " :%s:%s \n " , lev_config_name ( l ) , s , split_total_blocks * bs , split_free_blocks * bs , split - > fstype , split - > fslabel ) ;
2016-04-26 21:48:39 +02:00
}
2026-01-30 09:54:54 +01:00
log_tag ( " fsinfo_parity:%s:% " PRIu64 " :% " PRIu64 " \n " , lev_config_name ( l ) , state - > parity [ l ] . total_blocks * bs , state - > parity [ l ] . free_blocks * bs ) ;
2014-11-09 19:14:29 +01:00
}
2026-03-11 13:53:42 +01:00
/* for all extra disks */
for ( i = state - > extralist ; i ! = 0 ; i = i - > next ) {
struct snapraid_extra * extra = i - > data ;
uint64_t total_space ;
uint64_t free_space ;
int ret ;
ret = fsinfo ( extra - > dir , 0 , 0 , & total_space , & free_space , extra - > fstype , sizeof ( extra - > fstype ) , extra - > fslabel , sizeof ( extra - > fslabel ) ) ;
if ( ret ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error accessing disk '%s' to retrieve file-system info. %s. \n " , extra - > dir , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-05-29 11:01:44 +02:00
log_tag ( " fsinfo_extra:%s:% " PRIu64 " :% " PRIu64 " \n " , esc_tag ( extra - > name ) , total_space , free_space ) ;
2026-03-11 13:53:42 +01:00
}
2026-04-27 15:43:06 +02:00
/*
* Note what we don ' t set need_write = 1 , because we don ' t want
* to update the content file only for the free space info .
*/
2014-11-09 19:14:29 +01:00
}
2011-10-01 19:18:32 +02:00
/**
2015-10-07 22:07:20 +02:00
* Check the content .
2011-10-01 19:18:32 +02:00
*/
static void state_content_check ( struct snapraid_state * state , const char * path )
{
tommy_node * i ;
2015-10-07 22:07:20 +02:00
/* check that any map has different name and position */
2014-09-27 00:32:39 +02:00
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
2011-10-01 19:18:32 +02:00
struct snapraid_map * map = i - > data ;
tommy_node * j ;
2014-09-27 00:32:39 +02:00
for ( j = i - > next ; j ! = 0 ; j = j - > next ) {
2011-10-01 19:18:32 +02:00
struct snapraid_map * other = j - > data ;
if ( strcmp ( map - > name , other - > name ) = = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Conflicting 'map' disk specification in '%s' \n " , path ) ;
2011-10-01 19:18:32 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-01 19:18:32 +02:00
}
if ( map - > position = = other - > position ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Conflicting 'map' index specification in '%s' \n " , path ) ;
2011-10-01 19:18:32 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2011-10-01 19:18:32 +02:00
}
2011-06-20 20:35:04 +02:00
}
}
2011-03-04 20:59:21 +01:00
}
2013-08-17 16:17:53 +02:00
/**
2026-09-09 22:31:47 +02:00
* Analyze if the position is REQUIRED , if parity is unsynced , and if it has
2026-09-09 23:21:59 +02:00
* DELETED or REBUILD blocks .
2013-11-01 08:11:17 +01:00
*
2026-09-09 22:31:47 +02:00
* A DELETED block makes parity invalid , but it counts as unsynced only when at
* least one disk still has a file at the same position . This is the same logic
* used by " status " to detect an incomplete sync .
2013-08-17 16:17:53 +02:00
*/
2026-09-09 23:21:59 +02:00
static int fs_position_analyze ( struct snapraid_state * state , block_off_t pos , int * is_unsynced , int * has_deleted , int * has_rebuild )
2013-08-17 16:17:53 +02:00
{
tommy_node * i ;
2026-09-09 22:31:47 +02:00
int one_file = 0 ;
int one_invalid = 0 ;
int one_deleted = 0 ;
2026-09-09 23:21:59 +02:00
int one_rebuild = 0 ;
2013-08-17 16:17:53 +02:00
2014-02-01 17:24:19 +01:00
/* check for each disk */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2013-08-17 16:17:53 +02:00
struct snapraid_disk * disk = i - > data ;
2016-01-16 14:26:05 +01:00
struct snapraid_block * block = fs_par2block_find ( disk , pos ) ;
2026-09-09 22:31:47 +02:00
unsigned block_state = block_state_get ( block ) ;
2013-08-17 16:17:53 +02:00
2014-02-01 17:24:19 +01:00
if ( block_has_file ( block ) )
2026-09-09 22:31:47 +02:00
one_file = 1 ;
if ( block_has_invalid_parity ( block ) )
one_invalid = 1 ;
if ( block_state = = BLOCK_STATE_DELETED )
one_deleted = 1 ;
2026-09-09 23:21:59 +02:00
if ( block_state = = BLOCK_STATE_REBUILD )
one_rebuild = 1 ;
2013-08-17 16:17:53 +02:00
}
2026-09-09 22:31:47 +02:00
* is_unsynced = one_file & & one_invalid ;
* has_deleted = one_deleted ;
2026-09-09 23:21:59 +02:00
* has_rebuild = one_rebuild ;
2026-09-09 22:31:47 +02:00
return one_file ;
2013-08-17 16:17:53 +02:00
}
2014-02-01 17:24:19 +01:00
/**
2015-10-07 22:07:20 +02:00
* Check if the info block is REQUIREQ .
2014-02-01 17:24:19 +01:00
*
* This is used to ensure that we keep the last check used for scrubbing .
2015-10-05 22:48:16 +02:00
* and that we add it when importing old context files .
2014-02-01 17:24:19 +01:00
*
* Note that you can have position without info blocks , for example
2014-03-04 19:34:55 +01:00
* if all the blocks are not synced .
2014-02-01 17:24:19 +01:00
*
* Note also that not requiring an info block , doesn ' t mean that if present it
* can be discarded .
*/
2015-10-30 19:06:24 +01:00
static int fs_info_is_required ( struct snapraid_state * state , block_off_t pos )
2014-02-01 17:24:19 +01:00
{
tommy_node * i ;
/* check for each disk */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-02-01 17:24:19 +01:00
struct snapraid_disk * disk = i - > data ;
2016-01-16 14:26:05 +01:00
struct snapraid_block * block = fs_par2block_find ( disk , pos ) ;
2014-02-01 17:24:19 +01:00
2014-03-04 19:34:55 +01:00
/* if we have at least one synced file, the info is required */
2014-02-01 17:24:19 +01:00
if ( block_state_get ( block ) = = BLOCK_STATE_BLK )
return 1 ;
}
return 0 ;
}
2015-10-30 19:06:24 +01:00
static void fs_position_clear_deleted ( struct snapraid_state * state , block_off_t pos )
2014-02-01 17:24:19 +01:00
{
tommy_node * i ;
/* check for each disk if block is really used */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2014-02-01 17:24:19 +01:00
struct snapraid_disk * disk = i - > data ;
2016-01-16 14:26:05 +01:00
struct snapraid_block * block = fs_par2block_find ( disk , pos ) ;
2014-02-01 17:24:19 +01:00
/* if the block is deleted */
if ( block_state_get ( block ) = = BLOCK_STATE_DELETED ) {
/* set it to empty */
2015-10-04 22:02:05 +02:00
fs_deallocate ( disk , pos ) ;
2014-02-01 17:24:19 +01:00
}
}
}
2013-08-17 16:17:53 +02:00
/**
2015-10-07 22:07:20 +02:00
* Check if a block position in a disk is deleted .
2013-08-17 16:17:53 +02:00
*/
2026-09-09 23:16:19 +02:00
static inline int fs_is_block_deleted ( struct snapraid_disk * disk , block_off_t pos )
2013-08-17 16:17:53 +02:00
{
2016-01-16 14:26:05 +01:00
struct snapraid_block * block = fs_par2block_find ( disk , pos ) ;
2013-08-17 16:17:53 +02:00
return block_state_get ( block ) = = BLOCK_STATE_DELETED ;
}
2026-02-02 15:39:39 +01:00
/**
* Check if a block parity needs sync
*
* This is the same logic used in " status " to detect an incomplete " sync " ,
* that ignores invalid block , if they are not used by a file in any disk .
*
* This means that DELETED blocks won ' t necessarily imply an invalid parity .
*/
static int fs_is_block_unsynced ( struct snapraid_state * state , block_off_t pos )
{
int one_invalid = 0 ;
int one_valid = 0 ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
struct snapraid_block * block = fs_par2block_find ( disk , pos ) ;
if ( block_has_file ( block ) )
one_valid = 1 ;
if ( block_has_invalid_parity ( block ) )
one_invalid = 1 ;
if ( one_invalid & & one_valid )
return 1 ;
}
return 0 ;
}
2013-08-17 16:17:53 +02:00
/**
2026-08-14 11:07:15 +02:00
* Flush the file checking the final CRC and abort on error .
2013-08-17 16:17:53 +02:00
* We exploit the fact that the CRC is always stored in the last 4 bytes .
2026-08-14 11:07:15 +02:00
*
* Notice that on corruption , decoding error , or CRC mismatch we intentionally
* exit with a fatal error rather than automatically falling back to another
* content file . Automatic fallback could silently mask disk or memory corruption
* or inadvertently load a stale state . We require the user to explicitly rename
* or delete the corrupted file so SnapRAID will fall back to the surviving copy
* on the next run .
2013-08-17 16:17:53 +02:00
*/
static void decoding_error ( const char * path , STREAM * f )
{
unsigned char buf [ 4 ] ;
uint32_t crc_stored ;
uint32_t crc_computed ;
if ( seof ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Unexpected end of content file '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
2026-08-14 11:07:15 +02:00
log_fatal ( ECONTENT , " The content file '%s' is truncated! \n " , path ) ;
log_fatal ( ECONTENT , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( ECONTENT , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
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-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reading the content file '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
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
}
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Error decoding '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
2013-08-17 16:17:53 +02:00
2015-10-28 19:36:24 +01:00
if ( sdeplete ( f , buf ) ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Failed to flush content file '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
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-08-17 16:17:53 +02:00
/* get the stored crc from the last four bytes */
crc_stored = buf [ 0 ] | ( uint32_t ) buf [ 1 ] < < 8 | ( uint32_t ) buf [ 2 ] < < 16 | ( uint32_t ) buf [ 3 ] < < 24 ;
/* get the computed crc */
crc_computed = scrc ( f ) ;
/* adjust the stored crc to include itself */
2013-08-19 12:45:12 +02:00
crc_stored = crc32c ( crc_stored , buf , 4 ) ;
2013-08-17 16:17:53 +02:00
if ( crc_computed ! = crc_stored ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " CRC mismatch in '%s' \n " , path ) ;
2026-08-14 11:07:15 +02:00
log_fatal ( ECONTENT , " The content file '%s' is damaged or corrupted (CRC mismatch)! \n " , path ) ;
log_fatal ( ECONTENT , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( ECONTENT , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " The CRC of the file is correct! \n " ) ;
2011-04-25 13:43:51 +02:00
}
2013-08-17 16:17:53 +02:00
}
2011-04-25 13:43:51 +02:00
2026-09-09 18:25:29 +02:00
enum state_block_hash_mode {
STATE_BLOCK_HASH_READ ,
STATE_BLOCK_HASH_ZERO ,
STATE_BLOCK_HASH_DISCARD
} ;
/**
* Read and initialize a complete run of file blocks .
*
* CRC is computed when the stream buffer is filled . Advancing the stream
* pointer directly over cached hashes therefore preserves the same integrity
* check performed by sread ( ) .
*/
static int state_read_block_run ( STREAM * f , struct snapraid_file * file , block_off_t file_pos , block_off_t count , unsigned state , enum state_block_hash_mode hash_mode )
{
unsigned char * block_ptr = ( unsigned char * ) file_block ( file , file_pos ) ;
size_t block_stride = block_sizeof ( ) ;
unsigned char discard_hash [ HASH_MAX ] ;
if ( hash_mode = = STATE_BLOCK_HASH_ZERO ) {
while ( count ) {
struct snapraid_block * block = ( struct snapraid_block * ) block_ptr ;
block_state_set ( block , state ) ;
hash_zero_set ( block - > hash ) ;
block_ptr + = block_stride ;
- - count ;
}
return 0 ;
}
while ( count ) {
size_t available = ( size_t ) ( f - > end - f - > pos ) ;
block_off_t cached_count = available / BLOCK_HASH_SIZE ;
unsigned char * input ;
if ( cached_count > count )
cached_count = count ;
if ( cached_count = = 0 ) {
struct snapraid_block * block = ( struct snapraid_block * ) block_ptr ;
int ret ;
block_state_set ( block , state ) ;
if ( hash_mode = = STATE_BLOCK_HASH_DISCARD ) {
ret = sread ( f , discard_hash , BLOCK_HASH_SIZE ) ;
hash_invalid_set ( block - > hash ) ;
} else {
ret = sread ( f , block - > hash , BLOCK_HASH_SIZE ) ;
}
if ( ret < 0 )
return - 1 ;
block_ptr + = block_stride ;
- - count ;
continue ;
}
input = f - > pos ;
for ( block_off_t i = 0 ; i < cached_count ; + + i ) {
struct snapraid_block * block = ( struct snapraid_block * ) block_ptr ;
block_state_set ( block , state ) ;
if ( hash_mode = = STATE_BLOCK_HASH_DISCARD )
hash_invalid_set ( block - > hash ) ;
else
2026-09-09 23:40:37 +02:00
hash_copy ( block - > hash , input ) ;
2026-09-09 18:25:29 +02:00
input + = BLOCK_HASH_SIZE ;
block_ptr + = block_stride ;
}
f - > pos = input ;
count - = cached_count ;
}
return 0 ;
}
2026-09-09 22:31:47 +02:00
/**
* Enable or disable lock - free access to the extent trees of all disks .
* Callers must use this helper in matched , local pairs around operations that
* are known to be the only extent - tree users .
*/
static void fs_single_thread ( struct snapraid_state * state , int single_thread )
{
# if HAVE_THREAD
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
disk - > single_thread = single_thread ;
}
# else
( void ) state ;
( void ) single_thread ;
# endif
}
2015-10-28 22:04:04 +01:00
static void state_read_content ( struct snapraid_state * state , const char * path , STREAM * f )
2013-08-17 16:17:53 +02:00
{
block_off_t blockmax ;
2026-06-03 23:06:27 +02:00
uint64_t count_file ;
uint64_t count_hardlink ;
uint64_t count_symlink ;
uint64_t count_dir ;
uint64_t count_bad ;
uint64_t count_rehash ;
uint64_t count_unsynced ;
uint64_t count_unscrubbed ;
2013-08-17 16:17:53 +02:00
int crc_checked ;
2026-08-26 21:19:59 +02:00
int has_block_size ;
2026-08-27 22:27:52 +02:00
int has_blockmax ;
2026-08-26 21:19:59 +02:00
int has_hash_size ;
2026-08-27 18:13:27 +02:00
int has_hash ;
int has_prevhash ;
2026-08-31 10:37:19 +02:00
int has_info ;
2026-09-09 23:49:21 +02:00
int has_invalid_parity ;
2013-08-17 16:17:53 +02:00
char buffer [ PATH_MAX ] ;
int ret ;
2013-08-23 22:35:14 +02:00
tommy_array disk_mapping ;
uint32_t mapping_max ;
2026-02-08 22:56:22 +01:00
tommy_hashdyn bucket_hash ;
2013-08-17 16:17:53 +02:00
blockmax = 0 ;
count_file = 0 ;
count_hardlink = 0 ;
count_symlink = 0 ;
count_dir = 0 ;
2026-01-16 00:42:24 +01:00
count_bad = 0 ;
count_rehash = 0 ;
2026-02-02 15:39:39 +01:00
count_unsynced = 0 ;
count_unscrubbed = 0 ;
2013-08-17 16:17:53 +02:00
crc_checked = 0 ;
2026-08-26 21:19:59 +02:00
has_block_size = 0 ;
2026-08-27 22:27:52 +02:00
has_blockmax = 0 ;
2026-08-26 21:19:59 +02:00
has_hash_size = 0 ;
2026-08-27 18:13:27 +02:00
has_hash = 0 ;
has_prevhash = 0 ;
2026-08-31 10:37:19 +02:00
has_info = 0 ;
2026-09-09 23:49:21 +02:00
has_invalid_parity = 0 ;
2013-08-23 22:35:14 +02:00
mapping_max = 0 ;
tommy_array_init ( & disk_mapping ) ;
2026-02-08 22:56:22 +01:00
tommy_hashdyn_init ( & bucket_hash ) ;
2013-08-17 16:17:53 +02:00
2013-08-19 11:19:26 +02:00
ret = sread ( f , buffer , 12 ) ;
2013-08-25 17:54:39 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid header! \n " ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2014-11-09 19:14:29 +01:00
/*
* File format versions :
* - SNAPCNT1 / SnapRAID 4.0 First version .
* - SNAPCNT2 / SnapRAID 7.0 Adds entries ' M ' and ' P ' , to add free_blocks support .
* The previous ' m ' entry is now deprecated , but supported for importing .
* Similarly for text file , we add ' mapping ' and ' parity ' deprecating ' map ' .
2016-04-07 22:37:11 +02:00
* - SNAPCNT3 / SnapRAID 11.0 Adds entry ' y ' for hash size .
2016-04-26 21:48:39 +02:00
* - SNAPCNT3 / SnapRAID 11.0 Adds entry ' Q ' for multi parity file .
* The previous ' P ' entry is now deprecated , but supported for importing .
2026-08-28 12:11:05 +02:00
* - SNAPCNT4 adds :
* - entry ' d ' for deallocated files ;
* - block type ' r ' / BLOCK_STATE_REBUILD for persistent in - place parity
* rebuilds .
2014-11-09 19:14:29 +01:00
*/
if ( memcmp ( buffer , " SNAPCNT1 \n \3 \0 \0 " , 12 ) ! = 0
2016-04-07 22:37:11 +02:00
& & memcmp ( buffer , " SNAPCNT2 \n \3 \0 \0 " , 12 ) ! = 0
& & memcmp ( buffer , " SNAPCNT3 \n \3 \0 \0 " , 12 ) ! = 0
2026-04-27 14:39:38 +02:00
& & memcmp ( buffer , " SNAPCNT4 \n \3 \0 \0 " , 12 ) ! = 0
2016-04-07 22:37:11 +02:00
) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-25 17:54:39 +02:00
if ( memcmp ( buffer , " SNAPCNT " , 7 ) ! = 0 ) {
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid header! \n " ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2013-08-25 17:54:39 +02:00
} else {
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " The content file '%s' was generated with a newer version of SnapRAID! \n " , path ) ;
2015-12-12 18:17:30 +01:00
exit ( EXIT_FAILURE ) ;
2013-08-25 17:54:39 +02:00
}
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-25 17:54:39 +02:00
}
2013-08-17 16:17:53 +02:00
while ( 1 ) {
int c ;
/* read the command */
c = sgetc ( f ) ;
if ( c = = EOF ) {
break ;
}
if ( c = = ' f ' ) {
/* file */
char sub [ PATH_MAX ] ;
uint64_t v_size ;
uint64_t v_mtime_sec ;
uint32_t v_mtime_nsec ;
uint64_t v_inode ;
2026-08-14 00:17:58 +02:00
block_off_t v_idx ;
2013-08-17 16:17:53 +02:00
struct snapraid_file * file ;
struct snapraid_disk * disk ;
2013-08-23 22:35:14 +02:00
uint32_t mapping ;
2013-08-17 16:17:53 +02:00
2013-08-23 22:35:14 +02:00
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: File mapping index out of range \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2013-08-17 16:17:53 +02:00
ret = sgetb64 ( f , & v_size ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 20:38:14 +02:00
/* ensure the unsigned serialized size fits in signed data_off_t before conversion. */
if ( v_size > INT64_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid file size % " PRIu64 " ! \n " , v_size ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-01-12 23:03:11 +01:00
if ( state - > block_size = = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Zero blocksize \n " ) ;
2016-01-12 23:03:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-08-27 22:27:52 +02:00
if ( ! has_blockmax ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Missing 'blockmax' before file in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2013-08-19 11:19:26 +02:00
/* check for impossible file size to avoid to crash for a too big allocation */
if ( v_size / state - > block_size > blockmax ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-19 11:19:26 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: File size too big! \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-19 11:19:26 +02:00
}
2013-08-17 16:17:53 +02:00
ret = sgetb64 ( f , & v_mtime_sec ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32 ( f , & v_mtime_nsec ) ;
2026-08-26 22:26:35 +02:00
if ( ret < 0 | | v_mtime_nsec > 1000000000 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-22 21:30:19 +02:00
/* STAT_NSEC_INVALID is encoded as 0 */
if ( v_mtime_nsec = = 0 )
v_mtime_nsec = STAT_NSEC_INVALID ;
else
- - v_mtime_nsec ;
2013-08-17 16:17:53 +02:00
ret = sgetb64 ( f , & v_inode ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetbs ( f , sub , sizeof ( sub ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( sub ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid file '%s'! \n " , sub ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 17:56:44 +02:00
/*
* Allocate without initializing blocks . The runs below cover the whole
* file and set every state and hash before exposing the loaded state .
*/
2026-09-07 21:16:55 +02:00
file = file_alloc ( state - > block_size , sub , v_size , v_mtime_sec , v_mtime_nsec , v_inode ) ;
2013-08-17 16:17:53 +02:00
/* insert the file in the file containers */
2026-07-30 16:20:26 +02:00
if ( file - > inode ! = INODE_INVALID )
tommy_hashdyn_insert ( & disk - > inodeset , & file - > nodeset , file , file_inode_hash ( file - > inode ) ) ;
2013-08-17 16:17:53 +02:00
tommy_hashdyn_insert ( & disk - > pathset , & file - > pathset , file , file_path_hash ( file - > sub ) ) ;
2014-02-12 20:02:35 +01:00
tommy_hashdyn_insert ( & disk - > stampset , & file - > stampset , file , file_stamp_hash ( file - > size , file - > mtime_sec , file - > mtime_nsec ) ) ;
2013-08-17 16:17:53 +02:00
tommy_list_insert_tail ( & disk - > filelist , & file - > nodelist , file ) ;
2015-10-07 22:07:20 +02:00
/* read all the blocks */
2013-08-17 16:17:53 +02:00
v_idx = 0 ;
while ( v_idx < file - > blockmax ) {
block_off_t v_pos ;
2026-06-03 23:06:27 +02:00
block_off_t v_count ;
2026-09-09 18:06:09 +02:00
block_off_t v_file_pos ;
2026-09-09 18:25:29 +02:00
unsigned v_state ;
enum state_block_hash_mode hash_mode ;
2013-08-17 16:17:53 +02:00
2013-08-19 11:19:26 +02:00
/* get the "subcommand */
2013-08-17 16:17:53 +02:00
c = sgetc ( f ) ;
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_pos ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_count ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 18:06:09 +02:00
if ( v_count = = 0 | | v_count > file - > blockmax | | v_idx > file - > blockmax - v_count ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Block number out of range \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-06-03 19:00:24 +02:00
if ( v_count > blockmax | | v_pos > blockmax - v_count ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2015-02-19 19:47:22 +01:00
decoding_error ( path , f ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Block size % " PRIu64 " /% " PRIu64 " ! \n " , blockmax , v_pos + v_count ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2026-06-03 19:00:24 +02:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 18:25:29 +02:00
hash_mode = STATE_BLOCK_HASH_READ ;
switch ( c ) {
case ' b ' :
v_state = BLOCK_STATE_BLK ;
break ;
case ' r ' :
v_state = BLOCK_STATE_REBUILD ;
break ;
case ' n ' :
/* deprecated NEW blocks are converted to CHG ones with a ZERO hash */
v_state = BLOCK_STATE_CHG ;
hash_mode = STATE_BLOCK_HASH_ZERO ;
break ;
case ' g ' :
v_state = BLOCK_STATE_CHG ;
break ;
case ' p ' :
if ( state - > opt . force_nocopy ) {
/* discard stored copy hashes and convert REP blocks to CHG */
v_state = BLOCK_STATE_CHG ;
hash_mode = STATE_BLOCK_HASH_DISCARD ;
2014-02-01 17:57:26 +01:00
} else {
2026-09-09 18:25:29 +02:00
v_state = BLOCK_STATE_REP ;
2014-06-05 23:06:13 +02:00
}
2026-09-09 18:25:29 +02:00
break ;
default :
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Invalid block type! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2014-06-05 23:06:13 +02:00
2026-09-09 23:49:21 +02:00
/* all file block states except BLK require a parity update */
if ( v_state ! = BLOCK_STATE_BLK )
has_invalid_parity = 1 ;
2026-09-09 18:25:29 +02:00
v_file_pos = v_idx ;
ret = state_read_block_run ( f , file , v_file_pos , v_count , v_state , hash_mode ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 18:25:29 +02:00
v_idx + = v_count ;
2026-09-09 18:06:09 +02:00
/* set the parity association for the whole run */
fs_allocate ( disk , v_pos , file , v_file_pos , v_count ) ;
2013-08-17 16:17:53 +02:00
}
/* stat */
+ + count_file ;
} else if ( c = = ' i ' ) {
/* "inf" command */
snapraid_info info ;
2026-06-03 23:06:27 +02:00
block_off_t v_pos ;
2025-10-08 12:42:40 +02:00
uint64_t v_oldest ;
2013-08-17 16:17:53 +02:00
2026-08-27 22:27:52 +02:00
if ( has_info | | ! has_blockmax ) {
2026-08-31 10:37:19 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-08-27 22:27:52 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'info' in the content file! \n " ) ;
2026-08-31 10:37:19 +02:00
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_info = 1 ;
2025-10-08 12:42:40 +02:00
ret = sgetb64 ( f , & v_oldest ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-27 21:17:22 +02:00
/* ensure the unsigned serialized timestamp fits in signed time_t before conversion. */
if ( v_oldest > INT64_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid info oldest timestamp % " PRIu64 " ! \n " , v_oldest ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
v_pos = 0 ;
while ( v_pos < blockmax ) {
int bad ;
2015-08-27 18:02:12 +02:00
int rehash ;
int justsynced ;
2025-10-08 12:42:40 +02:00
uint64_t t64 ;
2013-08-17 16:17:53 +02:00
uint32_t flag ;
2026-06-03 23:06:27 +02:00
block_off_t v_count ;
2013-08-17 16:17:53 +02:00
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_count ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-06-03 19:00:24 +02:00
if ( v_count > blockmax | | v_pos > blockmax - v_count ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2015-02-19 19:47:22 +01:00
decoding_error ( path , f ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Info size % " PRIu64 " /% " PRIu64 " ! \n " , blockmax , v_pos + v_count ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
ret = sgetb32 ( f , & flag ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* if there is an info */
if ( ( flag & 1 ) ! = 0 ) {
/* read the time */
2025-10-08 12:42:40 +02:00
ret = sgetb64 ( f , & t64 ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-27 21:17:22 +02:00
/* ensure the timestamp addition fits in signed time_t without overflow. */
if ( t64 > INT64_MAX - v_oldest ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid info timestamp % " PRIu64 " ! \n " , t64 ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
/* analyze the flags */
bad = ( flag & 2 ) ! = 0 ;
rehash = ( flag & 4 ) ! = 0 ;
2015-08-27 18:02:12 +02:00
justsynced = ( flag & 8 ) ! = 0 ;
2013-08-17 16:17:53 +02:00
2026-01-16 00:42:24 +01:00
if ( bad )
2026-02-02 15:39:39 +01:00
count_bad + = v_count ;
2026-01-16 00:42:24 +01:00
if ( rehash )
2026-02-02 15:39:39 +01:00
count_rehash + = v_count ;
if ( justsynced )
count_unscrubbed + = v_count ;
2026-01-16 00:42:24 +01:00
2013-08-17 16:17:53 +02:00
if ( rehash & & state - > prevhash = = HASH_UNDEFINED ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Missing previous checksum! \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2025-10-08 12:42:40 +02:00
info = info_make ( t64 + v_oldest , bad , rehash , justsynced ) ;
2026-08-26 22:17:50 +02:00
if ( ! info ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Missing info! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-02-08 22:56:22 +01:00
bucket_insert ( & bucket_hash , t64 + v_oldest , v_count , justsynced ) ;
2013-08-17 16:17:53 +02:00
2026-09-10 00:08:58 +02:00
/* insert the complete run in the info array */
info_set_run ( & state - > infoarr , v_pos , v_count , info ) ;
v_pos + = v_count ;
2026-08-26 22:17:50 +02:00
} else {
while ( v_count ) {
/* ensure that an info is present only for used positions */
if ( fs_info_is_required ( state , v_pos ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-21 22:03:09 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Missing info! \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-21 22:03:09 +02:00
}
2026-08-26 22:17:50 +02:00
/* go to next block */
+ + v_pos ;
- - v_count ;
}
2013-08-17 16:17:53 +02:00
}
}
2026-09-09 23:49:21 +02:00
/*
* A synchronized content has no invalid block state , so its unsynced
* count is certainly zero . Scan the extent maps only when needed .
*/
if ( has_invalid_parity ) {
for ( v_pos = 0 ; v_pos < blockmax ; + + v_pos )
if ( fs_is_block_unsynced ( state , v_pos ) )
+ + count_unsynced ;
}
2013-08-17 16:17:53 +02:00
} else if ( c = = ' h ' ) {
/* hole */
2026-06-03 23:06:27 +02:00
block_off_t v_pos ;
2013-08-17 16:17:53 +02:00
struct snapraid_disk * disk ;
2013-08-23 22:35:14 +02:00
uint32_t mapping ;
2013-08-17 16:17:53 +02:00
2013-08-23 22:35:14 +02:00
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Hole mapping index out of range \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2013-08-17 16:17:53 +02:00
2026-08-27 22:27:52 +02:00
if ( ! has_blockmax ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Missing 'blockmax' before hole in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
v_pos = 0 ;
2013-08-20 14:25:29 +02:00
while ( v_pos < blockmax ) {
2026-06-03 23:06:27 +02:00
block_off_t v_count ;
2015-10-01 23:22:21 +02:00
struct snapraid_file * deleted ;
2013-08-17 16:17:53 +02:00
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_count ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 18:06:09 +02:00
if ( v_count = = 0 | | v_count > blockmax | | v_pos > blockmax - v_count ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2015-02-19 19:47:22 +01:00
decoding_error ( path , f ) ;
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Hole size % " PRIu64 " /% " PRIu64 " ! \n " , blockmax , v_pos + v_count ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2016-01-02 16:52:20 +01:00
/* get the sub-command */
2013-08-19 11:19:26 +02:00
c = sgetc ( f ) ;
2013-08-17 16:17:53 +02:00
2013-08-19 11:19:26 +02:00
switch ( c ) {
case ' o ' :
/* if it's a run of deleted blocks */
2026-09-09 23:49:21 +02:00
has_invalid_parity = 1 ;
2015-10-01 23:22:21 +02:00
2026-08-27 17:38:07 +02:00
/* ensure the converted byte size fits in signed data_off_t without overflow */
if ( v_count > INT64_MAX / ( uint64_t ) state - > block_size ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid deleted block count % " PRIu64 " ! \n " , v_count ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-09-09 17:56:44 +02:00
/*
* Allocate without initializing blocks . This deleted run sets every
* state and hash before the file is inserted in the extent map .
*/
2026-09-07 21:16:55 +02:00
deleted = file_alloc ( state - > block_size , " <deleted> " , v_count * ( data_off_t ) state - > block_size , 0 , 0 , 0 ) ;
2015-10-05 21:40:44 +02:00
/* mark the file as deleted */
file_flag_set ( deleted , FILE_IS_DELETED ) ;
2015-10-01 23:22:21 +02:00
/* insert it in the list of deleted files */
tommy_list_insert_tail ( & disk - > deletedlist , & deleted - > nodelist , deleted ) ;
2026-09-09 18:25:29 +02:00
/* read all blocks in the deleted run */
ret = state_read_block_run ( f , deleted , 0 , v_count , BLOCK_STATE_DELETED , STATE_BLOCK_HASH_READ ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-09-09 18:06:09 +02:00
/* insert the whole deleted run in the extent map */
fs_allocate ( disk , v_pos , deleted , 0 , v_count ) ;
v_pos + = v_count ;
2013-08-19 11:19:26 +02:00
break ;
case ' O ' :
2013-08-17 16:17:53 +02:00
/* go to the next run */
v_pos + = v_count ;
2013-08-19 11:19:26 +02:00
break ;
2014-09-27 00:32:39 +02:00
default :
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-19 11:19:26 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid hole type! \n " ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
2026-04-27 14:39:38 +02:00
} else if ( c = = ' d ' ) {
/* dealloc */
2026-06-03 23:06:27 +02:00
block_off_t v_count ;
block_off_t v_pos ;
2026-04-27 14:39:38 +02:00
struct snapraid_disk * disk ;
uint32_t mapping ;
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Dealloc mapping index out of range \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_count ) ;
2026-04-27 14:39:38 +02:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
log_tag ( " content_info:dealloc:%s:% " PRIu64 " \n " , esc_tag ( disk - > name ) , v_count ) ;
2026-04-27 14:39:38 +02:00
for ( v_pos = 0 ; v_pos < v_count ; + + v_pos ) {
char sub [ PATH_MAX ] ;
uint64_t v_size ;
uint64_t v_mtime_sec ;
uint32_t v_mtime_nsec ;
ret = sgetbs ( f , sub , sizeof ( sub ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( sub ) ) {
2026-04-27 14:39:38 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid dealloc '%s'! \n " , sub ) ;
2026-04-27 14:39:38 +02:00
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
ret = sgetb64 ( f , & v_size ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-08-26 20:38:14 +02:00
/* ensure the unsigned serialized size fits in signed data_off_t before conversion. */
if ( v_size > INT64_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid deallocated file size % " PRIu64 " ! \n " , v_size ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-04-27 14:39:38 +02:00
ret = sgetb64 ( f , & v_mtime_sec ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
ret = sgetb32 ( f , & v_mtime_nsec ) ;
2026-08-26 22:26:35 +02:00
if ( ret < 0 | | v_mtime_nsec > 1000000000 ) {
2026-04-27 14:39:38 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
/* STAT_NSEC_INVALID is encoded as 0 */
if ( v_mtime_nsec = = 0 )
v_mtime_nsec = STAT_NSEC_INVALID ;
else
- - v_mtime_nsec ;
/* allocate the file */
struct snapraid_dealloc * dealloc = dealloc_alloc ( state - > block_size , sub , v_size , v_mtime_sec , v_mtime_nsec ) ;
log_tag ( " content_info:dealloc_entry:%s:%s:% " PRIu64 " :% " PRIu64 " :%u \n " , esc_tag ( disk - > name ) , esc_tag ( dealloc - > sub ) , dealloc - > size , dealloc - > mtime_sec , dealloc - > mtime_nsec ) ;
/* read all hashes */
for ( block_off_t k = 0 ; k < dealloc - > blockmax ; + + k ) {
unsigned char * hash = dealloc - > blockhash + k * BLOCK_HASH_SIZE ;
ret = sread ( f , hash , BLOCK_HASH_SIZE ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
}
/* insert the dealloc in the dealloc containers */
tommy_list_insert_tail ( & disk - > dealloclist , & dealloc - > nodelist , dealloc ) ;
}
2013-08-17 16:17:53 +02:00
} else if ( c = = ' s ' ) {
/* symlink */
char sub [ PATH_MAX ] ;
char linkto [ PATH_MAX ] ;
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink ;
2013-08-17 16:17:53 +02:00
struct snapraid_disk * disk ;
2013-08-23 22:35:14 +02:00
uint32_t mapping ;
2013-08-17 16:17:53 +02:00
2013-08-23 22:35:14 +02:00
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Symlink mapping index out of range \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2013-08-17 16:17:53 +02:00
ret = sgetbs ( f , sub , sizeof ( sub ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( sub ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid symlink '%s'! \n " , sub ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2015-12-18 19:41:49 +01:00
ret = sgetbs ( f , linkto , sizeof ( linkto ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the link as symbolic link */
2015-10-08 22:19:52 +02:00
slink = link_alloc ( sub , linkto , FILE_IS_SYMLINK ) ;
2013-08-17 16:17:53 +02: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-08-17 16:17:53 +02:00
/* stat */
+ + count_symlink ;
} else if ( c = = ' a ' ) {
/* hardlink */
char sub [ PATH_MAX ] ;
char linkto [ PATH_MAX ] ;
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink ;
2013-08-17 16:17:53 +02:00
struct snapraid_disk * disk ;
2013-08-23 22:35:14 +02:00
uint32_t mapping ;
2013-08-17 16:17:53 +02:00
2013-08-23 22:35:14 +02:00
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Hardlink mapping index out of range! \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2013-08-17 16:17:53 +02:00
ret = sgetbs ( f , sub , sizeof ( sub ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( sub ) ) {
2015-12-18 19:41:49 +01:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid hardlink '%s'! \n " , sub ) ;
2015-12-18 19:41:49 +01:00
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
ret = sgetbs ( f , linkto , sizeof ( linkto ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( linkto ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid hardlink target '%s' for '%s'! \n " , linkto , sub ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the link as hard link */
2015-10-08 22:19:52 +02:00
slink = link_alloc ( sub , linkto , FILE_IS_HARDLINK ) ;
2013-08-17 16:17:53 +02: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-08-17 16:17:53 +02:00
/* stat */
+ + count_hardlink ;
} else if ( c = = ' r ' ) {
/* dir */
char sub [ PATH_MAX ] ;
struct snapraid_dir * dir ;
struct snapraid_disk * disk ;
2013-08-23 22:35:14 +02:00
uint32_t mapping ;
2013-08-17 16:17:53 +02:00
2013-08-23 22:35:14 +02:00
ret = sgetb32 ( f , & mapping ) ;
if ( ret < 0 | | mapping > = mapping_max ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-03-19 15:08:41 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Dir mapping index out of range! \n " ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
disk = tommy_array_get ( & disk_mapping , mapping ) ;
2013-08-17 16:17:53 +02:00
ret = sgetbs ( f , sub , sizeof ( sub ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-26 22:42:08 +02:00
if ( ! path_is_sub ( sub ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-08-26 22:42:08 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid dir '%s'! \n " , sub ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* allocate the dir */
dir = dir_alloc ( sub ) ;
/* 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 ) ;
/* stat */
+ + count_dir ;
} else if ( c = = ' c ' ) {
2026-08-27 18:13:27 +02:00
if ( has_hash | | mapping_max ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'hash' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_hash = 1 ;
2013-08-19 11:19:26 +02:00
/* get the subcommand */
2013-08-17 16:17:53 +02:00
c = sgetc ( f ) ;
2013-08-19 11:19:26 +02:00
2013-08-17 16:17:53 +02:00
switch ( c ) {
case ' u ' :
state - > hash = HASH_MURMUR3 ;
break ;
case ' k ' :
state - > hash = HASH_SPOOKY2 ;
break ;
2014-09-27 00:32:39 +02:00
default :
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid checksum! \n " ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* read the seed */
2016-04-07 21:39:02 +02:00
ret = sread ( f , state - > hashseed , HASH_MAX ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else if ( c = = ' C ' ) {
2026-08-27 18:13:27 +02:00
if ( has_prevhash | | mapping_max ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'prevhash' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_prevhash = 1 ;
2016-01-02 16:52:20 +01:00
/* get the sub-command */
2013-08-17 16:17:53 +02:00
c = sgetc ( f ) ;
2013-08-19 11:19:26 +02:00
2013-08-17 16:17:53 +02:00
switch ( c ) {
case ' u ' :
state - > prevhash = HASH_MURMUR3 ;
break ;
case ' k ' :
state - > prevhash = HASH_SPOOKY2 ;
break ;
2014-09-27 00:32:39 +02:00
default :
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid checksum! \n " ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* read the seed */
2016-04-07 21:39:02 +02:00
ret = sread ( f , state - > prevhashseed , HASH_MAX ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
} else if ( c = = ' z ' ) {
2016-04-07 22:37:11 +02:00
uint32_t block_size ;
2013-08-17 16:17:53 +02:00
2026-08-26 21:19:59 +02:00
if ( has_block_size | | mapping_max ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'blocksize' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_block_size = 1 ;
2016-04-07 22:37:11 +02:00
ret = sgetb32 ( f , & block_size ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2016-04-07 22:37:11 +02:00
if ( block_size = = 0 ) {
2016-01-12 23:03:11 +01:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Zero 'blocksize' specification in the content file! \n " ) ;
2016-01-12 23:03:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-11-26 18:43:12 +01:00
/* without configuration, auto assign the block size */
if ( state - > no_conf ) {
2016-04-07 22:37:11 +02:00
state - > block_size = block_size ;
2013-11-26 18:43:12 +01:00
}
2016-04-07 22:37:11 +02:00
if ( block_size ! = state - > block_size ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Mismatching 'blocksize' specification in the content file! \n " ) ;
log_fatal ( EUSER , " Please restore the 'blocksize' value in the configuration file to '%u' \n " , block_size / KIBI ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
} else if ( c = = ' y ' ) {
2026-06-04 00:34:51 +02:00
uint64_t hash_size ;
2016-04-07 22:37:11 +02:00
2026-08-26 21:19:59 +02:00
if ( has_hash_size | | mapping_max ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'hashsize' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_hash_size = 1 ;
2026-06-04 00:34:51 +02:00
ret = sgetb64 ( f , & hash_size ) ;
2016-04-07 22:37:11 +02:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-09-08 13:31:02 +02:00
if ( hash_size = = 2 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Unsupported 'hashsize 2' specification in the content file! The minimum supported hash size is 4. \n " ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( hash_size < 4 | | hash_size > HASH_MAX ) {
2016-04-07 22:37:11 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid 'hashsize' specification in the content file! \n " ) ;
2016-04-07 22:37:11 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-08-26 21:19:59 +02:00
/* without configuration, auto assign the hash size */
2016-04-07 22:37:11 +02:00
if ( state - > no_conf ) {
BLOCK_HASH_SIZE = hash_size ;
}
2026-05-03 22:29:11 +02:00
if ( hash_size ! = BLOCK_HASH_SIZE ) {
2016-04-07 22:37:11 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Mismatching 'hashsize' specification in the content file! \n " ) ;
2026-06-04 00:34:51 +02:00
log_fatal ( EUSER , " Please restore the 'hashsize' value in the configuration file to '% " PRIu64 " ' \n " , hash_size ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-20 14:25:29 +02:00
} else if ( c = = ' x ' ) {
2026-08-27 22:27:52 +02:00
if ( has_blockmax | | mapping_max ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Duplicate or misplaced 'blockmax' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
has_blockmax = 1 ;
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & blockmax ) ;
2013-08-20 14:25:29 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-20 14:25:29 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-20 14:25:29 +02:00
}
2026-08-27 16:07:59 +02:00
if ( blockmax > BLOCK_MAX ) {
2026-08-26 22:17:50 +02:00
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-08-27 16:07:59 +02:00
# if SIZE_MAX == UINT32_MAX
2026-08-26 22:17:50 +02:00
log_fatal ( ESOFT , " Array with % " PRIu64 " blocks in '%s' is too large for a 32-bit build. Use a 64-bit build or increase the block size. \n " , blockmax , path ) ;
2026-08-27 16:07:59 +02:00
# else
log_fatal ( ESOFT , " Array with % " PRIu64 " blocks in '%s' is too large. Increase the block size. \n " , blockmax , path ) ;
# endif
2026-08-26 22:17:50 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2014-11-09 19:14:29 +01:00
} else if ( c = = ' m ' | | c = = ' M ' ) {
2013-08-17 16:17:53 +02:00
struct snapraid_map * map ;
char uuid [ UUID_MAX ] ;
2026-06-03 23:06:27 +02:00
uint32_t v_idx ;
block_off_t v_total_blocks ;
block_off_t v_free_blocks ;
2013-08-23 22:35:14 +02:00
struct snapraid_disk * disk ;
2013-08-17 16:17:53 +02:00
2026-08-27 22:29:21 +02:00
if ( mapping_max > = RAID_DATA_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Too many data disk mappings in the content file! \n " ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2013-08-17 16:17:53 +02:00
ret = sgetbs ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-06-03 23:06:27 +02:00
ret = sgetb32 ( f , & v_idx ) ;
2013-08-17 16:17:53 +02:00
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-27 16:21:35 +02:00
if ( v_idx > = RAID_DATA_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Invalid disk position '%u' in the content file! \n " , v_idx ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2014-11-09 19:14:29 +01:00
/* from SnapRAID 7.0 the 'M' command includes the free space */
if ( c = = ' M ' ) {
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_total_blocks ) ;
2014-11-09 19:14:29 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_free_blocks ) ;
2014-11-09 19:14:29 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2025-12-22 00:03:38 +01:00
2026-01-30 09:54:54 +01:00
log_tag ( " content_data:%s:% " PRIi64 " :% " PRIi64 " \n " ,
2026-05-29 11:01:44 +02:00
esc_tag ( buffer ) ,
2025-12-22 00:03:38 +01:00
v_total_blocks * ( uint64_t ) state - > block_size ,
v_free_blocks * ( uint64_t ) state - > block_size ) ;
2014-11-09 19:14:29 +01:00
} else {
v_total_blocks = 0 ;
v_free_blocks = 0 ;
}
2013-08-17 16:17:53 +02:00
/* read the uuid */
ret = sgetbs ( f , uuid , sizeof ( uuid ) ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-01-30 09:54:54 +01:00
log_tag ( " content_data_split:%s:%s \n " ,
2026-05-29 11:01:44 +02:00
esc_tag ( buffer ) ,
2025-12-18 12:10:20 +01:00
uuid ) ;
2013-08-23 22:35:14 +02:00
/* find the disk */
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , buffer ) ;
if ( ! disk ) {
/* search by UUID if renamed */
disk = find_disk_by_uuid ( state , uuid ) ;
if ( disk ) {
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " WARNING! Renaming disk '%s' to '%s' \n " , buffer , disk - > name ) ;
2015-12-28 13:55:22 +01:00
/* write the new state with the new name */
state - > need_write = 1 ;
}
}
2013-08-23 22:35:14 +02:00
if ( ! disk ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-23 22:35:14 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Disk '%s' with uuid '%s' not present in the configuration file! \n " , buffer , uuid ) ;
log_fatal ( EUSER , " If you have removed it from the configuration file, please restore it \n " ) ;
2015-12-30 22:21:48 +01:00
/* if it's a command without UUID, it cannot autorename using UUID */
if ( state - > opt . skip_disk_access )
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " If you have renamed it, run 'sync' to update the new name \n " ) ;
2013-08-23 22:35:14 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-23 22:35:14 +02:00
}
2026-06-03 23:06:27 +02:00
map = map_alloc ( disk - > name , v_idx , v_total_blocks , v_free_blocks , uuid ) ;
2015-12-28 13:55:22 +01:00
tommy_list_insert_tail ( & state - > maplist , & map - > node , map ) ;
2013-08-23 22:35:14 +02:00
/* insert in the mapping vector */
tommy_array_grow ( & disk_mapping , mapping_max + 1 ) ;
tommy_array_set ( & disk_mapping , mapping_max , disk ) ;
+ + mapping_max ;
2014-11-09 19:14:29 +01:00
} else if ( c = = ' P ' ) {
2026-04-27 15:43:06 +02:00
/*
* From SnapRAID 7.0 the ' P ' command includes the free space
* from SnapRAID 11.0 the ' P ' command is deprecated by ' Q '
*/
2016-04-26 21:48:39 +02:00
char v_uuid [ UUID_MAX ] ;
2014-11-09 19:14:29 +01:00
uint32_t v_level ;
2026-06-03 23:06:27 +02:00
block_off_t v_total_blocks ;
block_off_t v_free_blocks ;
2014-11-09 19:14:29 +01:00
ret = sgetb32 ( f , & v_level ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_total_blocks ) ;
2014-11-09 19:14:29 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_free_blocks ) ;
2014-11-09 19:14:29 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2016-04-26 21:48:39 +02:00
ret = sgetbs ( f , v_uuid , sizeof ( v_uuid ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2016-07-26 20:56:21 +02:00
if ( v_level > = LEV_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid parity level '%u' in the configuration file! \n " , v_level ) ;
2016-07-26 20:56:21 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity:%s:% " PRIi64 " :% " PRIi64 " \n " ,
2025-12-22 00:03:38 +01:00
lev_config_name ( v_level ) ,
v_total_blocks * ( uint64_t ) state - > block_size ,
v_free_blocks * ( uint64_t ) state - > block_size ) ;
2016-04-26 21:48:39 +02:00
/* auto configure if configuration is missing */
2016-07-26 20:56:21 +02:00
if ( state - > no_conf ) {
if ( v_level > = state - > level )
state - > level = v_level + 1 ;
}
2016-04-26 21:48:39 +02:00
/* if we use this parity entry */
if ( v_level < state - > level ) {
2017-03-26 12:02:18 +02:00
/* if the configuration has more splits, keep them */
if ( state - > parity [ v_level ] . split_mac < 1 )
state - > parity [ v_level ] . split_mac = 1 ;
2016-04-26 21:48:39 +02:00
/* set the parity info */
2026-06-28 22:47:58 +02:00
if ( state - > no_conf ) {
pathcpy ( state - > parity [ v_level ] . split_map [ 0 ] . uuid , sizeof ( state - > parity [ v_level ] . split_map [ 0 ] . uuid ) , v_uuid ) ;
}
pathcpy ( state - > parity [ v_level ] . split_map [ 0 ] . content_uuid , sizeof ( state - > parity [ v_level ] . split_map [ 0 ] . content_uuid ) , v_uuid ) ;
2016-04-26 21:48:39 +02:00
state - > parity [ v_level ] . total_blocks = v_total_blocks ;
state - > parity [ v_level ] . free_blocks = v_free_blocks ;
2025-12-11 12:49:44 +01:00
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity_split:%s:%s:%s:% " PRIi64 " \n " ,
lev_config_name ( v_level ) , /* P command always has a single split */
2026-06-28 22:47:58 +02:00
state - > parity [ v_level ] . split_map [ 0 ] . content_uuid ,
2026-05-29 11:01:44 +02:00
esc_tag ( state - > parity [ v_level ] . split_map [ 0 ] . path ) ,
2025-12-11 12:49:44 +01:00
state - > parity [ v_level ] . split_map [ 0 ] . size ) ;
2016-04-26 21:48:39 +02:00
}
} else if ( c = = ' Q ' ) {
/* from SnapRAID 11.0 the 'Q' command include size info and multi file support */
uint32_t v_level ;
2026-06-03 23:06:27 +02:00
block_off_t v_total_blocks ;
block_off_t v_free_blocks ;
2016-04-26 21:48:39 +02:00
uint32_t v_split_mac ;
unsigned s ;
ret = sgetb32 ( f , & v_level ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_total_blocks ) ;
2016-04-26 21:48:39 +02:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-06-03 23:06:27 +02:00
ret = sgetb64 ( f , & v_free_blocks ) ;
2016-04-26 21:48:39 +02:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
ret = sgetb32 ( f , & v_split_mac ) ;
2014-11-09 19:14:29 +01:00
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
2026-05-20 00:17:46 +02:00
if ( v_split_mac > SPLIT_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Too many splits '%u' in the content file! \n " , v_split_mac ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-07-26 20:56:21 +02:00
if ( v_level > = LEV_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid parity level '%u' in the configuration file! \n " , v_level ) ;
2016-07-26 20:56:21 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2014-11-09 19:37:25 +01:00
/* auto configure if configuration is missing */
2016-06-25 17:17:51 +02:00
if ( state - > no_conf ) {
2016-07-26 20:56:21 +02:00
if ( v_level > = state - > level )
2016-06-25 17:17:51 +02:00
state - > level = v_level + 1 ;
if ( state - > parity [ v_level ] . split_mac < v_split_mac )
state - > parity [ v_level ] . split_mac = v_split_mac ;
}
2014-11-09 19:37:25 +01:00
2014-11-09 19:14:29 +01:00
/* if we use this parity entry */
if ( v_level < state - > level ) {
/* set the parity info */
state - > parity [ v_level ] . total_blocks = v_total_blocks ;
state - > parity [ v_level ] . free_blocks = v_free_blocks ;
}
2016-04-26 21:48:39 +02:00
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity:%s:% " PRIi64 " :% " PRIi64 " \n " ,
2025-12-22 00:03:38 +01:00
lev_config_name ( v_level ) ,
v_total_blocks * ( uint64_t ) state - > block_size ,
v_free_blocks * ( uint64_t ) state - > block_size ) ;
2016-04-26 21:48:39 +02:00
for ( s = 0 ; s < v_split_mac ; + + s ) {
char v_path [ PATH_MAX ] ;
char v_uuid [ UUID_MAX ] ;
uint64_t v_size ;
ret = sgetbs ( f , v_path , sizeof ( v_path ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
ret = sgetbs ( f , v_uuid , sizeof ( v_uuid ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
ret = sgetb64 ( f , & v_size ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2026-08-26 20:38:14 +02:00
/* ensure the unsigned serialized size fits in signed data_off_t before conversion. */
if ( v_size > INT64_MAX ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( ECONTENT , " Internal inconsistency: Invalid split file size % " PRIu64 " ! \n " , v_size ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-04-26 21:48:39 +02:00
/* if we use this parity entry */
if ( v_level < state - > level ) {
2016-06-25 17:17:51 +02:00
/* if this split was removed from the configuration */
if ( s > = state - > parity [ v_level ] . split_mac ) {
/* if the file is used, we really need it */
if ( v_size ! = 0 ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " Parity '%s' misses used file '%u'! \n " , lev_config_name ( v_level ) , s ) ;
log_fatal ( EUSER , " If you have removed it from the configuration file, please restore it \n " ) ;
2016-06-25 17:17:51 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* otherwise we can drop it */
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " WARNING! Dropping from '%s' unused split '%u' \n " , lev_config_name ( v_level ) , s ) ;
2016-06-25 17:17:51 +02:00
} else {
2025-12-22 00:03:38 +01:00
char parity_name [ 64 ] ;
2026-06-28 22:47:58 +02:00
/* we copy the path and uuid only if without configuration file */
if ( state - > no_conf ) {
2016-06-25 17:17:51 +02:00
pathcpy ( state - > parity [ v_level ] . split_map [ s ] . path , sizeof ( state - > parity [ v_level ] . split_map [ s ] . path ) , v_path ) ;
2026-06-28 22:47:58 +02:00
pathcpy ( state - > parity [ v_level ] . split_map [ s ] . uuid , sizeof ( state - > parity [ v_level ] . split_map [ s ] . uuid ) , v_uuid ) ;
}
2016-06-25 17:17:51 +02:00
/* set the split info */
2026-06-28 22:47:58 +02:00
pathcpy ( state - > parity [ v_level ] . split_map [ s ] . content_uuid , sizeof ( state - > parity [ v_level ] . split_map [ s ] . content_uuid ) , v_uuid ) ;
2016-06-25 17:17:51 +02:00
state - > parity [ v_level ] . split_map [ s ] . size = v_size ;
2026-01-05 15:55:38 +01:00
if ( s = = 0 )
2025-12-22 00:03:38 +01:00
pathcpy ( parity_name , sizeof ( parity_name ) , lev_config_name ( v_level ) ) ;
else
pathprint ( parity_name , sizeof ( parity_name ) , " %s/%u " , lev_config_name ( v_level ) , s ) ;
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity_split:%s:%s:%s:% " PRIi64 " \n " ,
2025-12-22 00:03:38 +01:00
parity_name ,
2026-06-28 22:47:58 +02:00
state - > parity [ v_level ] . split_map [ s ] . content_uuid ,
2026-05-29 11:01:44 +02:00
esc_tag ( state - > parity [ v_level ] . split_map [ s ] . path ) ,
2016-06-25 17:17:51 +02:00
state - > parity [ v_level ] . split_map [ s ] . size ) ;
}
2016-04-26 21:48:39 +02:00
}
}
2013-08-17 16:17:53 +02:00
} else if ( c = = ' N ' ) {
uint32_t crc_stored ;
uint32_t crc_computed ;
/* get the crc before reading it from the file */
crc_computed = scrc ( f ) ;
ret = sgetble32 ( f , & crc_stored ) ;
if ( ret < 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
/* here don't call decoding_error() because it's too late to get the crc */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reading the CRC in '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
2026-08-14 11:07:15 +02:00
log_fatal ( errno , " The content file '%s' is damaged or corrupted! \n " , path ) ;
log_fatal ( errno , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( errno , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if ( crc_stored ! = crc_computed ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
/* here don't call decoding_error() because it's too late to get the crc */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " CRC mismatch in '%s' \n " , path ) ;
2026-08-14 11:07:15 +02:00
log_fatal ( ECONTENT , " The content file '%s' is damaged or corrupted (CRC mismatch)! \n " , path ) ;
log_fatal ( ECONTENT , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( ECONTENT , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
crc_checked = 1 ;
2026-08-17 17:23:47 +02:00
/* trailing bytes would lie outside the content boundary verified by the terminal CRC */
c = sgetc ( f ) ;
if ( c ! = EOF ) {
/* LCOV_EXCL_START */
log_fatal ( ECONTENT , " Unexpected data after CRC in content file '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
log_fatal ( ECONTENT , " The content file '%s' is damaged or corrupted! \n " , path ) ;
log_fatal ( ECONTENT , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( ECONTENT , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
break ;
2013-08-17 16:17:53 +02:00
} else {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2013-08-17 16:17:53 +02:00
decoding_error ( path , f ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Invalid command '%c'! \n " , ( char ) c ) ;
2015-12-12 18:17:30 +01:00
os_abort ( ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
2013-08-23 22:35:14 +02:00
tommy_array_done ( & disk_mapping ) ;
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reading the content file '%s' at offset % " PRIi64 " \n " , path , stell ( f ) ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if ( ! crc_checked ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Reached the end of '%s' without finding the expected CRC \n " , path ) ;
2026-08-14 11:07:15 +02:00
log_fatal ( ECONTENT , " The content file '%s' is truncated or damaged! \n " , path ) ;
log_fatal ( ECONTENT , " To recover, rename or delete it and rerun the command. \n " ) ;
log_fatal ( ECONTENT , " SnapRAID will automatically fall back to the next healthy copy. \n " ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-08-27 22:27:52 +02:00
if ( ! has_blockmax ) {
/* LCOV_EXCL_START */
decoding_error ( path , f ) ;
log_fatal ( EINTERNAL , " Internal inconsistency: Missing 'blockmax' in the content file! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
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 read " ) ;
2013-08-17 16:17:53 +02:00
/* check that the stored parity size matches the loaded state */
2015-02-01 18:48:50 +01:00
if ( blockmax ! = parity_allocated_size ( state ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_fatal ( EINTERNAL , " Internal inconsistency: Parity size % " PRIu64 " /% " PRIu64 " in '%s' at offset % " PRIi64 " \n " , blockmax , parity_allocated_size ( state ) , path , stell ( f ) ) ;
2014-02-22 07:47:34 +01:00
if ( state - > opt . skip_content_check ) {
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " Overriding. \n " ) ;
2015-02-01 18:48:50 +01:00
blockmax = parity_allocated_size ( state ) ;
2014-02-22 07:47:34 +01:00
} else {
exit ( EXIT_FAILURE ) ;
}
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-10-21 19:43:01 +02:00
2026-06-03 23:06:27 +02:00
msg_verbose ( " %8 " PRIu64 " files \n " , count_file ) ;
msg_verbose ( " %8 " PRIu64 " hardlinks \n " , count_hardlink ) ;
msg_verbose ( " %8 " PRIu64 " symlinks \n " , count_symlink ) ;
msg_verbose ( " %8 " PRIu64 " empty dirs \n " , count_dir ) ;
2026-01-16 00:42:24 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " content_info:file:% " PRIu64 " \n " , count_file ) ;
log_tag ( " content_info:hardlink:% " PRIu64 " \n " , count_hardlink ) ;
log_tag ( " content_info:symlink:% " PRIu64 " \n " , count_symlink ) ;
log_tag ( " content_info:dir_empty:% " PRIu64 " \n " , count_dir ) ;
2026-02-02 15:39:39 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " content_info:block:% " PRIu64 " \n " , blockmax ) ;
log_tag ( " content_info:block_bad:% " PRIu64 " \n " , count_bad ) ;
log_tag ( " content_info:block_rehash:% " PRIu64 " \n " , count_rehash ) ;
log_tag ( " content_info:block_unsynced:% " PRIu64 " \n " , count_unsynced ) ;
log_tag ( " content_info:block_unscrubbed:% " PRIu64 " \n " , count_unscrubbed ) ;
2026-02-02 15:39:39 +01:00
2026-02-08 22:56:22 +01:00
/* store the blocks counters */
state - > rehash_blocks = count_rehash ;
state - > bad_blocks = count_bad ;
2026-02-02 16:32:16 +01:00
state - > unsynced_blocks = count_unsynced ;
2026-02-08 22:56:22 +01:00
state - > unscrubbed_blocks = count_unscrubbed ;
/* store the bucket info list */
bucket_to_list ( & bucket_hash , & state - > bucketlist , & state - > bucketcount ) ;
tommy_hashdyn_done ( & bucket_hash ) ;
2013-08-17 16:17:53 +02:00
}
2015-10-29 23:37:58 +01:00
struct state_write_thread_context {
struct snapraid_state * state ;
2016-01-01 17:42:37 +01:00
# if HAVE_MT_WRITE
2021-05-25 22:08:12 +02:00
thread_id_t thread ;
2015-10-29 23:37:58 +01:00
# endif
/* input */
2026-08-28 12:11:05 +02:00
int content_version ;
2015-10-29 23:37:58 +01:00
block_off_t blockmax ;
time_t info_oldest ;
2018-09-25 22:17:21 +02:00
time_t info_now ;
2015-10-29 23:37:58 +01:00
int info_has_rehash ;
2026-09-09 22:31:47 +02:00
uint64_t count_unsynced ;
2016-01-01 11:05:11 +01:00
STREAM * f ;
2026-01-16 00:42:24 +01:00
int first ;
2026-09-09 19:28:46 +02:00
# if HAVE_MT_WRITE
const char * content ;
# endif
2026-02-08 22:56:22 +01:00
/* output (required to postpone the output to the terminal after the latest write) */
2015-10-29 23:37:58 +01:00
uint32_t crc ;
2026-06-03 23:06:27 +02:00
uint64_t count_file ;
uint64_t count_hardlink ;
uint64_t count_symlink ;
uint64_t count_dir ;
uint64_t count_bad ;
uint64_t count_rehash ;
uint64_t count_unscrubbed ;
2015-10-29 23:37:58 +01:00
} ;
2026-09-09 19:02:43 +02:00
/**
* Write all hashes in a contiguous run of file blocks .
*
* The source hashes are separated by the block state byte , so they cannot be
2026-09-10 00:28:24 +02:00
* copied with a single memcpy ( ) . Keep the stream pointer local while filling
* each buffered group to avoid the per - block stream call and field updates .
2026-09-09 19:02:43 +02:00
*/
static int state_write_block_run ( STREAM * f , struct snapraid_file * file , block_off_t file_pos , block_off_t count )
{
unsigned char * block_ptr = ( unsigned char * ) file_block ( file , file_pos ) ;
size_t block_stride = block_sizeof ( ) ;
while ( count ) {
size_t available = ( size_t ) ( f - > end - f - > pos ) ;
block_off_t cached_count = available / BLOCK_HASH_SIZE ;
unsigned char * output ;
if ( cached_count > count )
cached_count = count ;
if ( cached_count = = 0 ) {
struct snapraid_block * block = ( struct snapraid_block * ) block_ptr ;
if ( swrite ( block - > hash , BLOCK_HASH_SIZE , f ) ! = 0 )
return - 1 ;
block_ptr + = block_stride ;
- - count ;
continue ;
}
output = f - > pos ;
for ( block_off_t i = 0 ; i < cached_count ; + + i ) {
struct snapraid_block * block = ( struct snapraid_block * ) block_ptr ;
2026-09-09 23:40:37 +02:00
hash_copy ( output , block - > hash ) ;
2026-09-09 19:02:43 +02:00
output + = BLOCK_HASH_SIZE ;
block_ptr + = block_stride ;
}
f - > pos = output ;
count - = cached_count ;
}
return 0 ;
}
2015-10-29 23:37:58 +01:00
static void * state_write_thread ( void * arg )
2013-08-17 16:17:53 +02:00
{
2015-10-29 23:37:58 +01:00
struct state_write_thread_context * context = arg ;
struct snapraid_state * state = context - > state ;
block_off_t blockmax = context - > blockmax ;
time_t info_oldest = context - > info_oldest ;
2018-09-25 22:17:21 +02:00
time_t info_now = context - > info_now ;
2015-10-29 23:37:58 +01:00
int info_has_rehash = context - > info_has_rehash ;
2016-01-01 11:05:11 +01:00
STREAM * f = context - > f ;
2015-10-29 23:37:58 +01:00
uint32_t crc ;
2025-10-08 12:42:40 +02:00
uint64_t t64 ;
2026-06-03 23:06:27 +02:00
uint64_t count_file ;
uint64_t count_hardlink ;
uint64_t count_symlink ;
uint64_t count_dir ;
uint64_t count_bad ;
uint64_t count_rehash ;
uint64_t count_unscrubbed ;
2013-08-17 16:17:53 +02:00
tommy_node * i ;
block_off_t begin ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2026-02-08 22:56:22 +01:00
tommy_hashdyn bucket_hash ;
2026-09-09 19:28:46 +02:00
# if HAVE_MT_WRITE
uint64_t start ;
start = os_tick_ms ( ) ;
# endif
2013-08-17 16:17:53 +02:00
count_file = 0 ;
count_hardlink = 0 ;
count_symlink = 0 ;
count_dir = 0 ;
2026-01-16 00:42:24 +01:00
count_bad = 0 ;
count_rehash = 0 ;
2026-02-02 15:39:39 +01:00
count_unscrubbed = 0 ;
2026-02-08 22:56:22 +01:00
tommy_hashdyn_init ( & bucket_hash ) ;
2013-08-17 16:17:53 +02:00
2013-08-20 13:06:18 +02:00
/* write header */
2026-08-28 12:11:05 +02:00
if ( context - > content_version > = 4 )
2026-04-27 14:39:38 +02:00
swrite ( " SNAPCNT4 \n \3 \0 \0 " , 12 , f ) ;
2026-08-28 12:11:05 +02:00
else
swrite ( " SNAPCNT3 \n \3 \0 \0 " , 12 , f ) ;
2013-08-20 13:06:18 +02:00
2013-08-20 14:25:29 +02:00
/* write block size and block max */
2013-08-17 16:17:53 +02:00
sputc ( ' z ' , f ) ;
sputb32 ( state - > block_size , f ) ;
2013-08-20 14:25:29 +02:00
sputc ( ' x ' , f ) ;
2026-06-03 23:06:27 +02:00
sputb64 ( blockmax , f ) ;
2016-04-07 22:37:11 +02:00
2016-04-26 21:48:39 +02:00
/* hash size */
2025-12-15 10:57:37 +01:00
sputc ( ' y ' , f ) ;
2026-06-04 00:34:51 +02:00
sputb64 ( BLOCK_HASH_SIZE , f ) ;
2016-04-07 22:37:11 +02:00
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
sputc ( ' c ' , f ) ;
if ( state - > hash = = HASH_MURMUR3 ) {
sputc ( ' u ' , f ) ;
} else if ( state - > hash = = HASH_SPOOKY2 ) {
sputc ( ' k ' , f ) ;
} else {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Unexpected hash when writing the content file '%s'. \n " , serrorfile ( f ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2016-04-07 21:39:02 +02:00
swrite ( state - > hashseed , HASH_MAX , f ) ;
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* previous hash only present */
if ( state - > prevhash ! = HASH_UNDEFINED ) {
/* if at least one rehash tag found, we have to save the previous hash */
if ( info_has_rehash ) {
sputc ( ' C ' , f ) ;
if ( state - > prevhash = = HASH_MURMUR3 ) {
sputc ( ' u ' , f ) ;
2013-09-26 20:34:01 +02:00
} else if ( state - > prevhash = = HASH_SPOOKY2 ) {
2013-08-17 16:17:53 +02:00
sputc ( ' k ' , f ) ;
} else {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Unexpected prevhash when writing the content file '%s'. \n " , serrorfile ( f ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2016-04-07 21:39:02 +02:00
swrite ( state - > prevhashseed , HASH_MAX , f ) ;
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
}
/* for each map */
2014-09-27 00:32:39 +02:00
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
2013-08-17 16:17:53 +02:00
struct snapraid_map * map = i - > data ;
2013-08-23 22:35:14 +02:00
struct snapraid_disk * disk ;
/* find the disk for this mapping */
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , map - > name ) ;
2013-08-23 22:35:14 +02:00
if ( ! disk ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Unmapped disk '%s' \n " , map - > name ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-23 22:35:14 +02:00
2015-10-29 23:37:58 +01:00
/* save the mapping only if disk is mapped */
if ( disk - > mapping_idx ! = - 1 ) {
2026-06-28 23:35:44 +02:00
const char * uuid ;
2014-11-09 19:14:29 +01:00
sputc ( ' M ' , f ) ;
2013-08-23 22:35:14 +02:00
sputbs ( map - > name , f ) ;
sputb32 ( map - > position , f ) ;
2026-06-03 23:06:27 +02:00
sputb64 ( map - > total_blocks , f ) ;
sputb64 ( map - > free_blocks , f ) ;
2026-06-28 23:35:44 +02:00
/*
* If the probed uuid is empty , but disk access was skipped ( globally
* or individually ) , write the last known uuid . Otherwise , write the
* empty probed uuid .
*/
if ( disk - > uuid [ 0 ] = = 0 & & ( state - > opt . skip_disk_access | | disk - > skip_access ) ) {
uuid = map - > content_uuid ;
} else {
uuid = disk - > uuid ;
}
sputbs ( uuid , f ) ;
2026-01-16 00:42:24 +01:00
if ( context - > first ) {
2026-01-30 09:54:54 +01:00
log_tag ( " content_data:%s:% " PRIi64 " :% " PRIi64 " \n " ,
2026-05-29 11:01:44 +02:00
esc_tag ( map - > name ) ,
2026-01-16 00:42:24 +01:00
map - > total_blocks * ( uint64_t ) state - > block_size ,
map - > free_blocks * ( uint64_t ) state - > block_size ) ;
2026-01-30 09:54:54 +01:00
log_tag ( " content_data_split:%s:%s \n " ,
2026-05-29 11:01:44 +02:00
esc_tag ( map - > name ) ,
2026-06-28 23:35:44 +02:00
uuid ) ;
2026-01-16 00:42:24 +01:00
}
2013-08-23 22:35:14 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-23 22:35:14 +02:00
}
}
2013-08-17 16:17:53 +02:00
}
2014-11-09 19:14:29 +01:00
/* for each parity */
for ( l = 0 ; l < state - > level ; + + l ) {
2025-12-15 10:57:37 +01:00
sputc ( ' Q ' , f ) ;
sputb32 ( l , f ) ;
2026-06-03 23:06:27 +02:00
sputb64 ( state - > parity [ l ] . total_blocks , f ) ;
sputb64 ( state - > parity [ l ] . free_blocks , f ) ;
2026-01-16 00:42:24 +01:00
if ( context - > first ) {
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity:%s:% " PRIi64 " :% " PRIi64 " \n " ,
2026-01-16 00:42:24 +01:00
lev_config_name ( l ) ,
state - > parity [ l ] . total_blocks * ( uint64_t ) state - > block_size ,
state - > parity [ l ] . free_blocks * ( uint64_t ) state - > block_size ) ;
}
2025-12-15 10:57:37 +01:00
sputb32 ( state - > parity [ l ] . split_mac , f ) ;
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
2026-01-16 00:42:24 +01:00
char parity_name [ 64 ] ;
2026-06-28 22:47:58 +02:00
const char * uuid ;
2025-12-15 10:57:37 +01:00
sputbs ( state - > parity [ l ] . split_map [ s ] . path , f ) ;
2026-06-28 22:47:58 +02:00
/*
* If the probed uuid is not empty , write it . Otherwise , if parity access was
* skipped ( globally or individually ) , write the last known uuid read from the
* content file . Otherwise , write the empty probed uuid .
*/
if ( state - > parity [ l ] . split_map [ s ] . uuid [ 0 ] = = 0
& & ( state - > opt . skip_parity_access | | state - > parity [ l ] . skip_access ) ) {
uuid = state - > parity [ l ] . split_map [ s ] . content_uuid ;
} else {
uuid = state - > parity [ l ] . split_map [ s ] . uuid ;
}
sputbs ( uuid , f ) ;
2025-12-15 10:57:37 +01:00
sputb64 ( state - > parity [ l ] . split_map [ s ] . size , f ) ;
2026-01-16 00:42:24 +01:00
if ( s = = 0 )
pathcpy ( parity_name , sizeof ( parity_name ) , lev_config_name ( l ) ) ;
else
pathprint ( parity_name , sizeof ( parity_name ) , " %s/%u " , lev_config_name ( l ) , s ) ;
if ( context - > first ) {
2026-01-30 09:54:54 +01:00
log_tag ( " content_parity_split:%s:%s:%s:% " PRIi64 " \n " ,
2026-01-16 00:42:24 +01:00
parity_name ,
2026-06-28 22:47:58 +02:00
uuid ,
2026-08-17 19:02:15 +02:00
esc_tag ( state - > parity [ l ] . split_map [ s ] . path ) ,
2026-01-16 00:42:24 +01:00
state - > parity [ l ] . split_map [ s ] . size ) ;
}
2016-04-26 21:48:39 +02:00
}
2014-11-09 19:14:29 +01:00
if ( serror ( f ) ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-11-09 19:14:29 +01:00
/* LCOV_EXCL_STOP */
}
}
2013-08-17 16:17:53 +02:00
/* for each disk */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2013-08-17 16:17:53 +02:00
tommy_node * j ;
struct snapraid_disk * disk = i - > data ;
2013-08-23 22:35:14 +02:00
/* if the disk is not mapped, skip it */
2014-11-09 10:51:48 +01:00
if ( disk - > mapping_idx < 0 )
2013-08-23 22:35:14 +02:00
continue ;
2013-08-17 16:17:53 +02:00
/* for each file */
2014-09-27 00:32:39 +02:00
for ( j = disk - > filelist ; j ! = 0 ; j = j - > next ) {
2013-08-17 16:17:53 +02:00
struct snapraid_file * file = j - > data ;
2026-09-09 19:02:43 +02:00
size_t block_stride = block_sizeof ( ) ;
2013-08-17 16:17:53 +02:00
uint64_t size ;
uint64_t mtime_sec ;
int32_t mtime_nsec ;
uint64_t inode ;
size = file - > size ;
mtime_sec = file - > mtime_sec ;
mtime_nsec = file - > mtime_nsec ;
inode = file - > inode ;
sputc ( ' f ' , f ) ;
2014-11-09 10:51:48 +01:00
sputb32 ( disk - > mapping_idx , f ) ;
2013-08-17 16:17:53 +02:00
sputb64 ( size , f ) ;
sputb64 ( mtime_sec , f ) ;
2013-08-22 21:30:19 +02:00
/* encode STAT_NSEC_INVALID as 0 */
if ( mtime_nsec = = STAT_NSEC_INVALID )
sputb32 ( 0 , f ) ;
else
sputb32 ( mtime_nsec + 1 , f ) ;
2013-08-17 16:17:53 +02:00
sputb64 ( inode , f ) ;
sputbs ( file - > sub , f ) ;
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* for all the blocks of the file */
begin = 0 ;
while ( begin < file - > blockmax ) {
2026-09-09 19:02:43 +02:00
struct snapraid_block * block = file_block ( file , begin ) ;
unsigned char * end_block_ptr = ( unsigned char * ) block + block_stride ;
unsigned v_state = block_state_get ( block ) ;
2026-09-09 21:58:54 +02:00
block_off_t extent_count ;
block_off_t v_pos = fs_file2par_get_run ( disk , file , begin , & extent_count ) ;
block_off_t extent_end = begin + extent_count ;
2026-06-03 23:06:27 +02:00
block_off_t v_count ;
2014-09-27 00:32:39 +02:00
2013-08-17 16:17:53 +02:00
block_off_t end ;
/* find the end of run of blocks */
end = begin + 1 ;
2015-10-04 22:02:05 +02:00
while ( end < file - > blockmax ) {
2026-09-09 19:02:43 +02:00
block = ( struct snapraid_block * ) end_block_ptr ;
if ( v_state ! = block_state_get ( block ) )
2015-10-04 22:02:05 +02:00
break ;
2026-09-09 21:58:54 +02:00
/* Resolve the mapping only when crossing an extent boundary. */
if ( end = = extent_end ) {
block_off_t next_count ;
block_off_t next_pos = fs_file2par_get_run ( disk , file , end , & next_count ) ;
if ( v_pos + ( end - begin ) ! = next_pos )
break ;
extent_end + = next_count ;
}
2026-09-09 19:02:43 +02:00
end_block_ptr + = block_stride ;
2013-08-17 16:17:53 +02:00
+ + end ;
}
2015-10-04 22:02:05 +02:00
switch ( v_state ) {
2013-08-17 16:17:53 +02:00
case BLOCK_STATE_BLK :
sputc ( ' b ' , f ) ;
break ;
2026-08-28 12:11:05 +02:00
case BLOCK_STATE_REBUILD :
sputc ( ' r ' , f ) ;
break ;
2013-08-17 16:17:53 +02:00
case BLOCK_STATE_CHG :
sputc ( ' g ' , f ) ;
break ;
2014-02-01 17:57:26 +01:00
case BLOCK_STATE_REP :
sputc ( ' p ' , f ) ;
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: State for block % " PRIu64 " state %u \n " , v_pos , v_state ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-06-03 23:06:27 +02:00
sputb64 ( v_pos , f ) ;
2013-08-17 16:17:53 +02:00
v_count = end - begin ;
2026-06-03 23:06:27 +02:00
sputb64 ( v_count , f ) ;
2013-08-17 16:17:53 +02:00
2026-09-09 19:02:43 +02:00
/* write all hashes in the run */
if ( state_write_block_run ( f , file , begin , v_count ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
goto bail ;
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* next begin position */
begin = end ;
}
+ + count_file ;
}
/* for each link */
2014-09-27 00:32:39 +02:00
for ( j = disk - > linklist ; j ! = 0 ; j = j - > next ) {
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink = j - > data ;
2013-08-17 16:17:53 +02:00
2015-10-08 22:19:52 +02:00
switch ( link_flag_get ( slink , FILE_IS_LINK_MASK ) ) {
2013-08-17 16:17:53 +02:00
case FILE_IS_HARDLINK :
sputc ( ' a ' , f ) ;
+ + count_hardlink ;
break ;
case FILE_IS_SYMLINK :
sputc ( ' s ' , f ) ;
+ + count_symlink ;
break ;
}
2014-11-09 10:51:48 +01:00
sputb32 ( disk - > mapping_idx , f ) ;
2015-10-08 22:19:52 +02:00
sputbs ( slink - > sub , f ) ;
sputbs ( slink - > linkto , f ) ;
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
}
/* for each dir */
2014-09-27 00:32:39 +02:00
for ( j = disk - > dirlist ; j ! = 0 ; j = j - > next ) {
2013-08-17 16:17:53 +02:00
struct snapraid_dir * dir = j - > data ;
sputc ( ' r ' , f ) ;
2014-11-09 10:51:48 +01:00
sputb32 ( disk - > mapping_idx , f ) ;
2013-08-17 16:17:53 +02:00
sputbs ( dir - > sub , f ) ;
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
+ + count_dir ;
}
2013-08-23 22:35:14 +02:00
/* deleted blocks of the disk */
sputc ( ' h ' , f ) ;
2014-11-09 10:51:48 +01:00
sputb32 ( disk - > mapping_idx , f ) ;
2013-08-23 22:35:14 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-23 22:35:14 +02:00
}
2026-09-09 23:16:19 +02:00
2013-08-20 14:25:29 +02:00
begin = 0 ;
2026-09-09 23:16:19 +02:00
if ( tommy_list_empty ( & disk - > deletedlist ) ) {
/* the common case needs only one non-deleted run */
if ( blockmax ) {
sputb64 ( blockmax , f ) ;
sputc ( ' O ' , f ) ;
}
} else {
while ( begin < blockmax ) {
int is_deleted ;
block_off_t end ;
2013-08-20 14:25:29 +02:00
2026-09-09 23:16:19 +02:00
is_deleted = fs_is_block_deleted ( disk , begin ) ;
2013-08-17 16:17:53 +02:00
2026-09-09 23:16:19 +02:00
/* find the end of run of blocks */
end = begin + 1 ;
while ( end < blockmax
& & is_deleted = = fs_is_block_deleted ( disk , end )
) {
+ + end ;
}
2013-08-17 16:17:53 +02:00
2026-09-09 23:16:19 +02:00
sputb64 ( end - begin , f ) ;
2013-08-20 14:25:29 +02:00
2026-09-09 23:16:19 +02:00
if ( is_deleted ) {
/* write the run of deleted blocks with hash */
sputc ( ' o ' , f ) ;
2013-08-20 14:25:29 +02:00
2026-09-09 23:16:19 +02:00
/* write all the hashes */
while ( begin < end ) {
struct snapraid_block * block = fs_par2block_get ( disk , begin ) ;
2013-08-20 14:25:29 +02:00
2026-09-09 23:16:19 +02:00
swrite ( block - > hash , BLOCK_HASH_SIZE , f ) ;
2013-08-20 14:25:29 +02:00
2026-09-09 23:16:19 +02:00
+ + begin ;
}
} else {
/* used and empty blocks don't have a hash in this section */
sputc ( ' O ' , f ) ;
begin = end ;
2013-08-20 14:25:29 +02:00
}
}
2026-09-09 23:16:19 +02:00
}
2013-08-17 16:17:53 +02:00
2026-09-09 23:16:19 +02:00
if ( serror ( f ) ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
goto bail ;
/* LCOV_EXCL_STOP */
2013-08-20 14:25:29 +02:00
}
2026-04-27 14:39:38 +02:00
/* deallocated files of the disk */
if ( ! tommy_list_empty ( & disk - > dealloclist ) ) {
sputc ( ' d ' , f ) ;
sputb32 ( disk - > mapping_idx , f ) ;
2026-06-03 23:06:27 +02:00
block_off_t v_count = tommy_list_count ( & disk - > dealloclist ) ;
2026-04-27 14:39:38 +02:00
2026-06-03 23:06:27 +02:00
sputb64 ( v_count , f ) ;
2026-09-09 19:28:46 +02:00
if ( context - > first )
log_tag ( " content_info:dealloc:%s:% " PRIu64 " \n " , esc_tag ( disk - > name ) , v_count ) ;
2026-04-27 14:39:38 +02:00
/* for each file */
for ( j = tommy_list_head ( & disk - > dealloclist ) ; j ! = 0 ; j = j - > next ) {
struct snapraid_dealloc * dealloc = j - > data ;
sputbs ( dealloc - > sub , f ) ;
sputb64 ( dealloc - > size , f ) ;
sputb64 ( dealloc - > mtime_sec , f ) ;
/* encode STAT_NSEC_INVALID as 0 */
if ( dealloc - > mtime_nsec = = STAT_NSEC_INVALID )
sputb32 ( 0 , f ) ;
else
sputb32 ( dealloc - > mtime_nsec + 1 , f ) ;
2026-09-09 19:28:46 +02:00
if ( context - > first )
log_tag ( " content_info:dealloc_entry:%s:%s:% " PRIu64 " :% " PRIu64 " :%u \n " , esc_tag ( disk - > name ) , esc_tag ( dealloc - > sub ) , dealloc - > size , dealloc - > mtime_sec , dealloc - > mtime_nsec ) ;
2026-04-27 14:39:38 +02:00
2026-09-09 19:02:43 +02:00
/* deallocated hashes are already contiguous in memory */
swrite ( dealloc - > blockhash , ( size_t ) dealloc - > blockmax * BLOCK_HASH_SIZE , f ) ;
2026-04-27 14:39:38 +02:00
}
if ( serror ( f ) ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
goto bail ;
/* LCOV_EXCL_STOP */
}
} else {
2026-09-09 19:28:46 +02:00
if ( context - > first )
log_tag ( " content_info:dealloc:%s:0 \n " , esc_tag ( disk - > name ) ) ;
2026-04-27 14:39:38 +02:00
}
2013-08-20 14:25:29 +02:00
}
2013-08-17 16:17:53 +02:00
2013-08-20 14:25:29 +02:00
/* write the info for each block */
sputc ( ' i ' , f ) ;
2025-10-08 12:42:40 +02:00
/* ensure to write a 64 bit time */
t64 = info_oldest ;
sputb64 ( t64 , f ) ;
2013-08-20 14:25:29 +02:00
begin = 0 ;
while ( begin < blockmax ) {
snapraid_info info ;
block_off_t end ;
2026-02-02 15:39:39 +01:00
block_off_t count ;
2013-08-20 14:25:29 +02:00
time_t t ;
unsigned flag ;
2013-08-17 16:17:53 +02:00
2026-09-10 00:08:58 +02:00
/* find the complete run without resolving each array position */
count = info_get_run ( & state - > infoarr , begin , blockmax - begin , & info ) ;
end = begin + count ;
2026-06-03 23:06:27 +02:00
sputb64 ( count , f ) ;
2013-08-17 16:17:53 +02:00
2013-08-20 14:25:29 +02:00
/* if there is info */
if ( info ) {
/* other flags */
flag = 1 ; /* info is present */
2026-01-16 00:42:24 +01:00
if ( info_get_bad ( info ) ) {
2013-08-20 14:25:29 +02:00
flag | = 2 ;
2026-02-02 15:39:39 +01:00
if ( context - > first )
count_bad + = count ;
2026-01-16 00:42:24 +01:00
}
if ( info_get_rehash ( info ) ) {
2013-08-20 14:25:29 +02:00
flag | = 4 ;
2026-02-02 15:39:39 +01:00
if ( context - > first )
count_rehash + = count ;
2026-01-16 00:42:24 +01:00
}
2026-02-02 15:39:39 +01:00
if ( info_get_justsynced ( info ) ) {
2015-08-27 18:02:12 +02:00
flag | = 8 ;
2026-02-02 15:39:39 +01:00
if ( context - > first )
count_unscrubbed + = count ;
}
2013-08-20 14:25:29 +02:00
sputb32 ( flag , f ) ;
2013-08-17 16:17:53 +02:00
2018-09-25 22:17:21 +02:00
t = info_get_time ( info ) ;
2026-02-08 22:56:22 +01:00
if ( context - > first )
bucket_insert ( & bucket_hash , t , count , flag & 8 ) ;
2018-09-25 22:17:21 +02:00
/* truncate any time that is in the future */
if ( t > info_now )
t = info_now ;
2018-09-25 22:02:14 +02:00
/* the oldest info is computed only on required blocks, so it may not be the absolute oldest */
2018-09-25 22:17:21 +02:00
if ( t < info_oldest )
2018-09-25 22:02:14 +02:00
t = 0 ;
2018-09-25 22:17:21 +02:00
else
t - = info_oldest ;
2018-09-25 22:02:14 +02:00
2025-10-08 12:42:40 +02:00
/* ensure to write a 64 bit time */
t64 = t ;
sputb64 ( t64 , f ) ;
2013-08-20 14:25:29 +02:00
} else {
/* write a special 0 flag to mark missing info */
sputb32 ( 0 , f ) ;
}
2013-08-17 16:17:53 +02:00
2013-08-20 14:25:29 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2013-08-20 14:25:29 +02:00
/* next begin position */
begin = end ;
2013-08-17 16:17:53 +02:00
}
sputc ( ' N ' , f ) ;
2015-02-18 19:27:59 +01:00
/* flush data written to the disk */
if ( sflush ( f ) ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s' (in flush before crc). %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2015-02-18 19:27:59 +01:00
/* LCOV_EXCL_STOP */
}
/* get the file crc */
2015-10-29 23:37:58 +01:00
crc = scrc ( f ) ;
2015-02-18 19:27:59 +01:00
2015-10-29 23:37:58 +01:00
sputble32 ( crc , f ) ;
2013-08-17 16:17:53 +02:00
if ( serror ( f ) ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2026-02-18 19:48:30 +01:00
goto bail ;
2015-10-29 23:37:58 +01:00
/* LCOV_EXCL_STOP */
}
/* set output variables */
context - > crc = crc ;
context - > count_file = count_file ;
context - > count_hardlink = count_hardlink ;
context - > count_symlink = count_symlink ;
context - > count_dir = count_dir ;
2026-01-16 00:42:24 +01:00
context - > count_bad = count_bad ;
context - > count_rehash = count_rehash ;
2026-02-02 15:39:39 +01:00
context - > count_unscrubbed = count_unscrubbed ;
2015-10-29 23:37:58 +01:00
2026-02-08 22:56:22 +01:00
/* store the bucket info list */
if ( context - > first )
bucket_to_list ( & bucket_hash , & state - > bucketlist , & state - > bucketcount ) ;
2026-07-06 22:23:57 +02:00
/*
* Do not need to free the bucket entries in the success path because they
* are added only if context - > first is true , and they are already moved
* to the list .
*/
2026-02-18 19:48:30 +01:00
tommy_hashdyn_done ( & bucket_hash ) ;
2026-09-09 19:28:46 +02:00
# if HAVE_MT_WRITE
msg_progress ( " Saved %s in % " PRIu64 " ms \n " , context - > content , os_tick_ms ( ) - start ) ;
# endif
2015-10-29 23:37:58 +01:00
return 0 ;
2026-02-18 19:48:30 +01:00
bail :
2026-08-24 23:03:00 +02:00
tommy_hashdyn_foreach ( & bucket_hash , bucket_free ) ;
2026-02-18 19:48:30 +01:00
tommy_hashdyn_done ( & bucket_hash ) ;
return context ;
2015-10-29 23:37:58 +01:00
}
static void state_write_content ( struct snapraid_state * state , uint32_t * out_crc )
{
2016-01-01 17:42:37 +01:00
# if HAVE_MT_WRITE
2015-10-29 23:37:58 +01:00
int fail ;
2016-01-01 11:05:11 +01:00
int first ;
# else
STREAM * f ;
unsigned count_content ;
unsigned k ;
struct state_write_thread_context * context ;
void * retval ;
# endif
tommy_node * i ;
2015-10-29 23:37:58 +01:00
block_off_t blockmax ;
time_t info_oldest ;
2018-09-25 22:17:21 +02:00
time_t info_now ;
2026-09-07 10:56:48 +02:00
time_t content_mtime ;
2015-10-29 23:37:58 +01:00
int info_has_rehash ;
2026-09-09 23:21:59 +02:00
int state_has_rebuild ;
2015-10-29 23:37:58 +01:00
int mapping_idx ;
block_off_t idx ;
uint32_t crc ;
2026-06-03 23:06:27 +02:00
block_off_t count_file ;
block_off_t count_hardlink ;
block_off_t count_symlink ;
block_off_t count_dir ;
block_off_t count_bad ;
block_off_t count_rehash ;
block_off_t count_unsynced ;
block_off_t count_unscrubbed ;
2026-09-13 21:14:38 +02:00
# if !HAVE_MT_WRITE
uint64_t start ;
# endif
2015-10-29 23:37:58 +01:00
/* blocks of all array */
blockmax = parity_allocated_size ( state ) ;
2026-09-09 22:31:47 +02:00
/* preparation is the only extent-tree user until serialization starts */
fs_single_thread ( state , 1 ) ;
2016-01-02 16:52:20 +01:00
/* check the file-system on all disks */
2015-10-29 23:37:58 +01:00
state_fscheck ( state , " before write " ) ;
2026-04-27 15:43:06 +02:00
/*
* Clear the info for unused blocks
* and get some other info
*/
2015-10-29 23:37:58 +01:00
info_oldest = 0 ; /* oldest time in info */
2018-09-25 22:17:21 +02:00
info_now = time ( 0 ) ; /* get the present time */
2015-10-29 23:37:58 +01:00
info_has_rehash = 0 ; /* if there is a rehash info */
2026-09-09 23:21:59 +02:00
state_has_rebuild = 0 ;
2026-09-09 22:31:47 +02:00
count_unsynced = 0 ;
2015-10-29 23:37:58 +01:00
for ( idx = 0 ; idx < blockmax ; + + idx ) {
2026-09-09 22:31:47 +02:00
int is_unsynced ;
int has_deleted ;
2026-09-09 23:21:59 +02:00
int has_rebuild ;
2026-09-09 22:31:47 +02:00
2015-10-29 23:37:58 +01:00
/* if the position is used */
2026-09-09 23:21:59 +02:00
if ( fs_position_analyze ( state , idx , & is_unsynced , & has_deleted , & has_rebuild ) ) {
2015-10-29 23:37:58 +01:00
snapraid_info info = info_get ( & state - > infoarr , idx ) ;
2026-09-09 23:21:59 +02:00
if ( has_rebuild )
state_has_rebuild = 1 ;
2026-09-09 22:31:47 +02:00
if ( is_unsynced )
+ + count_unsynced ;
2015-10-29 23:37:58 +01:00
/* only if there is some info to store */
if ( info ) {
time_t info_time = info_get_time ( info ) ;
if ( ! info_oldest | | info_time < info_oldest )
info_oldest = info_time ;
if ( info_get_rehash ( info ) )
info_has_rehash = 1 ;
}
} else {
/* clear any previous info */
info_set ( & state - > infoarr , idx , 0 ) ;
2026-09-09 22:31:47 +02:00
/* and clear any deleted blocks, avoiding another scan in the common case */
if ( has_deleted )
fs_position_clear_deleted ( state , idx ) ;
2015-10-29 23:37:58 +01:00
}
}
2026-07-13 11:02:33 +02:00
if ( info_has_rehash & & state - > prevhash = = HASH_UNDEFINED ) {
/* LCOV_EXCL_START */
log_fatal ( EINTERNAL , " Internal inconsistency: Rehash blocks found but previous checksum is missing! \n " ) ;
os_abort ( ) ;
/* LCOV_EXCL_STOP */
}
2015-10-29 23:37:58 +01:00
/* map disks */
mapping_idx = 0 ;
for ( i = state - > maplist ; i ! = 0 ; i = i - > next ) {
struct snapraid_map * map = i - > data ;
struct snapraid_disk * disk ;
/* find the disk for this mapping */
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( state , map - > name ) ;
2015-10-29 23:37:58 +01:00
if ( ! disk ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: Unmapped disk '%s' \n " , map - > name ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2015-10-29 23:37:58 +01:00
/* LCOV_EXCL_STOP */
}
/* save the mapping only for not empty disks */
2016-01-01 10:00:18 +01:00
if ( ! fs_is_empty ( disk , blockmax ) ) {
2015-10-29 23:37:58 +01:00
/* assign the mapping index used to identify disks */
disk - > mapping_idx = mapping_idx ;
+ + mapping_idx ;
} else {
/* mark the disk as without mapping */
disk - > mapping_idx = - 1 ;
}
}
2026-08-28 12:11:05 +02:00
int content_version = 3 ;
/*
* Force at least version 3 as we want to always store the parity size .
* If there is a REBUILD block or a dealloc list , force version 4.
*/
2026-09-09 23:21:59 +02:00
if ( state_has_rebuild ) {
2026-08-28 12:11:05 +02:00
content_version = 4 ;
} else {
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
if ( ! tommy_list_empty ( & disk - > dealloclist ) ) {
content_version = 4 ;
break ;
}
}
}
2026-09-09 22:31:47 +02:00
fs_single_thread ( state , 0 ) ;
2026-09-13 21:14:38 +02:00
# if !HAVE_MT_WRITE
start = os_tick_ms ( ) ;
# endif
2016-01-01 17:42:37 +01:00
# if HAVE_MT_WRITE
2015-10-29 23:37:58 +01:00
/* start all writing threads */
2026-01-16 00:42:24 +01:00
first = 1 ;
2015-10-29 23:37:58 +01:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
struct state_write_thread_context * context ;
2016-01-01 11:05:11 +01:00
char tmp [ PATH_MAX ] ;
STREAM * f ;
msg_progress ( " Saving state to %s... \n " , content - > content ) ;
2026-05-29 11:01:44 +02:00
log_tag ( " content_write:%s \n " , esc_tag ( content - > content ) ) ;
2016-01-01 11:05:11 +01:00
pathprint ( tmp , sizeof ( tmp ) , " %s.tmp " , content - > content ) ;
2018-06-04 22:14:23 +02:00
/* ensure to delete a previous stale file */
if ( remove ( tmp ) ! = 0 ) {
if ( errno ! = ENOENT ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error removing the stale content file '%s'. %s. \n " , tmp , strerror ( errno ) ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2026-09-09 20:39:33 +02:00
f = sopen_write ( tmp , CONTENT_WRITE_ADVISE | STREAM_FLAGS_CRC ) ;
2016-01-01 11:05:11 +01:00
if ( f = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the temporary content file '%s'. %s. \n " , tmp , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-10-29 23:37:58 +01:00
/* allocate the thread context */
context = malloc_nofail ( sizeof ( struct state_write_thread_context ) ) ;
content - > context = context ;
/* initialize */
context - > state = state ;
2026-08-28 12:11:05 +02:00
context - > content_version = content_version ;
2015-10-29 23:37:58 +01:00
context - > blockmax = blockmax ;
context - > info_oldest = info_oldest ;
2018-09-25 22:17:21 +02:00
context - > info_now = info_now ;
2015-10-29 23:37:58 +01:00
context - > info_has_rehash = info_has_rehash ;
2026-09-09 22:31:47 +02:00
context - > count_unsynced = count_unsynced ;
2016-01-01 11:05:11 +01:00
context - > f = f ;
2026-01-16 00:42:24 +01:00
context - > first = first ;
2026-09-09 19:28:46 +02:00
context - > content = content - > content ;
2026-01-16 00:42:24 +01:00
first = 0 ;
2015-10-29 23:37:58 +01:00
2021-05-25 22:08:12 +02:00
thread_create ( & context - > thread , state_write_thread , context ) ;
2015-10-29 23:37:58 +01:00
i = i - > next ;
}
2026-09-07 10:56:48 +02:00
/* join all threads before starting the durable finalization */
2015-10-29 23:37:58 +01:00
fail = 0 ;
2026-09-07 10:56:48 +02:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
struct state_write_thread_context * context = content - > context ;
void * retval ;
thread_join ( context - > thread , & retval ) ;
if ( retval ) {
/* LCOV_EXCL_START */
fail = 1 ;
/* LCOV_EXCL_STOP */
}
i = i - > next ;
}
/* abort on failure before finalizing any temporary content file */
if ( fail ) {
/* LCOV_EXCL_START */
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/*
* All temporary content files of this publication use the same modification time .
* SnapRAID guarantees that two state_write ( ) publications cannot occur in the
* same second due to synchronization delays and the overall execution model .
*/
content_mtime = time ( 0 ) ;
2015-10-29 23:37:58 +01:00
first = 1 ;
crc = 0 ;
count_file = 0 ;
count_hardlink = 0 ;
count_symlink = 0 ;
count_dir = 0 ;
2026-01-16 00:42:24 +01:00
count_bad = 0 ;
count_rehash = 0 ;
2026-02-02 15:39:39 +01:00
count_unscrubbed = 0 ;
2026-09-07 10:56:48 +02:00
/* make all temporary content files durable only after all writers have terminated */
2015-10-29 23:37:58 +01:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
struct state_write_thread_context * context = content - > context ;
2026-09-07 10:56:48 +02:00
STREAM * f = context - > f ;
2015-10-29 23:37:58 +01:00
2026-09-07 10:56:48 +02:00
/*
* Use the sequence fflush ( ) - > fsync ( ) - > fclose ( ) - > rename ( ) to ensure
* than even in a system crash event we have one valid copy of the file .
*/
if ( sflush ( f ) ! = 0 ) {
2015-10-31 16:29:08 +01:00
/* LCOV_EXCL_START */
2026-09-07 10:56:48 +02:00
log_fatal ( errno , " Error writing the content file '%s', in flush(). %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
2015-10-31 16:29:08 +01:00
/* LCOV_EXCL_STOP */
2026-09-07 10:56:48 +02:00
}
2016-01-01 11:05:11 +01:00
2026-09-07 10:56:48 +02:00
if ( smtime ( f , content_mtime , 0 ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error setting the modification time of the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-01-01 11:05:11 +01:00
# if HAVE_FSYNC
2026-09-07 10:56:48 +02:00
if ( ssync ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error writing the content file '%s' in sync(). %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-01-01 11:05:11 +01:00
# endif
2026-09-07 10:56:48 +02:00
if ( sclose ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error closing the content file. %s. \n " , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( first ) {
first = 0 ;
crc = context - > crc ;
count_file = context - > count_file ;
count_hardlink = context - > count_hardlink ;
count_symlink = context - > count_symlink ;
count_dir = context - > count_dir ;
count_bad = context - > count_bad ;
count_rehash = context - > count_rehash ;
count_unsynced = context - > count_unsynced ;
count_unscrubbed = context - > count_unscrubbed ;
} else {
if ( crc ! = context - > crc ) {
2016-01-01 11:05:11 +01:00
/* LCOV_EXCL_START */
2026-09-07 10:56:48 +02:00
log_fatal ( ECONTENT , " Different CRCs writing content streams. \n " ) ;
log_fatal ( ECONTENT , " DANGER! Your RAM memory is broken! DO NOT PROCEED UNTIL FIXED! \n " ) ;
log_fatal ( ECONTENT , " Try running a memory test like http://www.memtest86.com/ \n " ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-10-29 23:37:58 +01:00
}
free ( context ) ;
i = i - > next ;
}
2016-01-01 11:05:11 +01:00
# else
/* count the content files */
count_content = 0 ;
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
msg_progress ( " Saving state to %s... \n " , content - > content ) ;
+ + count_content ;
i = i - > next ;
}
/* open all the content files */
2026-09-09 20:39:33 +02:00
f = sopen_multi_write ( count_content , CONTENT_WRITE_ADVISE | STREAM_FLAGS_CRC ) ;
2016-01-01 11:05:11 +01:00
if ( ! f ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the content files. \n " ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
k = 0 ;
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
char tmp [ PATH_MAX ] ;
pathprint ( tmp , sizeof ( tmp ) , " %s.tmp " , content - > content ) ;
2018-06-04 22:14:23 +02:00
/* ensure to delete a previous stale file */
if ( remove ( tmp ) ! = 0 ) {
if ( errno ! = ENOENT ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error removing the stale content file '%s'. %s. \n " , tmp , strerror ( errno ) ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2016-01-01 11:05:11 +01:00
if ( sopen_multi_file ( f , k , tmp ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the temporary content file '%s'. %s. \n " , tmp , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2018-06-04 22:14:23 +02:00
2016-01-01 11:05:11 +01:00
+ + k ;
i = i - > next ;
}
/* allocate the thread context */
context = malloc_nofail ( sizeof ( struct state_write_thread_context ) ) ;
/* initialize */
context - > state = state ;
2026-08-28 12:11:05 +02:00
context - > content_version = content_version ;
2016-01-01 11:05:11 +01:00
context - > blockmax = blockmax ;
context - > info_oldest = info_oldest ;
2018-09-25 22:17:21 +02:00
context - > info_now = info_now ;
2016-01-01 11:05:11 +01:00
context - > info_has_rehash = info_has_rehash ;
2026-09-09 22:31:47 +02:00
context - > count_unsynced = count_unsynced ;
2016-01-01 11:05:11 +01:00
context - > f = f ;
2026-01-16 00:42:24 +01:00
context - > first = 1 ;
2016-01-01 11:05:11 +01:00
2026-09-09 22:31:47 +02:00
/* this serializer is the only extent-tree user until it completes */
fs_single_thread ( state , 1 ) ;
2016-01-01 11:05:11 +01:00
retval = state_write_thread ( context ) ;
2026-09-09 22:31:47 +02:00
fs_single_thread ( state , 0 ) ;
2026-09-09 22:09:36 +02:00
2016-01-01 11:05:11 +01:00
/* abort on failure */
if ( retval ) {
/* LCOV_EXCL_START */
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-09-07 10:56:48 +02:00
/*
* All temporary content files of this publication use the same modification time .
* SnapRAID guarantees that two state_write ( ) publications cannot occur in the
* same second due to synchronization delays and the overall execution model .
*/
content_mtime = time ( 0 ) ;
2026-04-27 15:43:06 +02:00
/*
* Use the sequence fflush ( ) - > fsync ( ) - > fclose ( ) - > rename ( ) to ensure
* than even in a system crash event we have one valid copy of the file .
*/
2016-01-01 11:05:11 +01:00
if ( sflush ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s', in flush(). %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-09-07 10:56:48 +02:00
if ( smtime ( f , content_mtime , 0 ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error setting the modification time of the content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2016-01-01 11:05:11 +01:00
# if HAVE_FSYNC
if ( ssync ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error writing the content file '%s' in sync(). %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
# endif
if ( sclose ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error closing the content file. %s. \n " , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-09-13 21:14:38 +02:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
msg_progress ( " Saved %s in % " PRIu64 " seconds \n " , content - > content , ( os_tick_ms ( ) - start ) / 1000 ) ;
i = i - > next ;
}
2016-01-01 11:05:11 +01:00
crc = context - > crc ;
count_file = context - > count_file ;
count_hardlink = context - > count_hardlink ;
count_symlink = context - > count_symlink ;
count_dir = context - > count_dir ;
2026-01-16 00:42:24 +01:00
count_bad = context - > count_bad ;
count_rehash = context - > count_rehash ;
2026-02-02 15:39:39 +01:00
count_unsynced = context - > count_unsynced ;
count_unscrubbed = context - > count_unscrubbed ;
2016-01-01 11:05:11 +01:00
free ( context ) ;
# endif
2013-08-17 16:17:53 +02:00
2026-06-03 23:06:27 +02:00
msg_verbose ( " %8 " PRIu64 " files \n " , count_file ) ;
msg_verbose ( " %8 " PRIu64 " hardlinks \n " , count_hardlink ) ;
msg_verbose ( " %8 " PRIu64 " symlinks \n " , count_symlink ) ;
msg_verbose ( " %8 " PRIu64 " empty dirs \n " , count_dir ) ;
2015-10-29 23:37:58 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " content_info:file:% " PRIu64 " \n " , count_file ) ;
log_tag ( " content_info:hardlink:% " PRIu64 " \n " , count_hardlink ) ;
log_tag ( " content_info:symlink:% " PRIu64 " \n " , count_symlink ) ;
log_tag ( " content_info:dir_empty:% " PRIu64 " \n " , count_dir ) ;
2026-02-02 15:39:39 +01:00
2026-06-03 23:06:27 +02:00
log_tag ( " content_info:block:% " PRIu64 " \n " , blockmax ) ;
log_tag ( " content_info:block_bad:% " PRIu64 " \n " , count_bad ) ;
log_tag ( " content_info:block_rehash:% " PRIu64 " \n " , count_rehash ) ;
log_tag ( " content_info:block_unsynced:% " PRIu64 " \n " , count_unsynced ) ;
log_tag ( " content_info:block_unscrubbed:% " PRIu64 " \n " , count_unscrubbed ) ;
2026-02-02 15:39:39 +01:00
2026-02-08 22:56:22 +01:00
/* store the blocks counters */
state - > rehash_blocks = count_rehash ;
state - > bad_blocks = count_bad ;
2026-02-02 16:32:16 +01:00
state - > unsynced_blocks = count_unsynced ;
2026-02-08 22:56:22 +01:00
state - > unscrubbed_blocks = count_unscrubbed ;
2026-01-16 00:42:24 +01:00
2015-10-29 23:37:58 +01:00
* out_crc = crc ;
2013-08-17 16:17:53 +02:00
}
2026-04-09 15:15:48 +02:00
void state_probe ( struct snapraid_state * state )
{
char path [ PATH_MAX ] ;
struct stat st ;
tommy_node * node ;
/* iterate over all the available content files and check the first one present */
node = tommy_list_head ( & state - > contentlist ) ;
while ( node ) {
struct snapraid_content * content = node - > data ;
pathcpy ( path , sizeof ( path ) , content - > content ) ;
if ( stat ( path , & st ) = = 0 ) {
/* if found stop the search */
break ;
}
/* next content file */
node = node - > next ;
}
/* if not found, assume empty */
if ( ! node )
return ;
if ( ! state - > no_conf ) {
/* intentionally don't output the content path because it's not really read */
log_tag ( " content_info:probe_unixtime:% " PRId64 " \n " , ( int64_t ) st . st_mtime ) ;
log_flush ( ) ;
}
}
2013-08-17 16:17:53 +02:00
void state_read ( struct snapraid_state * state )
{
STREAM * f ;
char path [ PATH_MAX ] ;
struct stat st ;
tommy_node * node ;
int ret ;
int c ;
2026-08-14 11:07:15 +02:00
/*
* Iterate over all the available content files and load the first one present .
*
* Only missing files ( ENOENT ) trigger automatic failover to the next replica .
* If a file exists but is corrupted or damaged , reading will fail - fast with
* exit ( EXIT_FAILURE ) to alert the user to possible hardware or filesystem corruption .
* Once the user renames or removes the broken copy , the next run will fall back
* to the healthy replica and automatically re - create the missing one .
*/
2013-08-17 16:17:53 +02:00
f = 0 ;
node = tommy_list_head ( & state - > contentlist ) ;
while ( node ) {
struct snapraid_content * content = node - > data ;
pathcpy ( path , sizeof ( path ) , content - > content ) ;
2015-02-22 14:31:03 +01:00
msg_progress ( " Loading state from %s... \n " , path ) ;
2013-08-17 16:17:53 +02:00
2026-09-09 20:39:33 +02:00
f = sopen_read ( path , ADVISE_SEQUENTIAL | STREAM_FLAGS_CRC ) ;
2013-08-17 16:17:53 +02:00
if ( f ! = 0 ) {
2026-04-09 15:15:48 +02:00
/* if found stop the search */
2013-08-17 16:17:53 +02:00
break ;
} else {
/* if it's real error of an existing file, abort */
if ( errno ! = ENOENT ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the content file '%s'. %s. \n " , path , strerror ( errno ) ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
/* otherwise continue */
if ( node - > next ) {
2026-05-30 17:54:31 +02:00
log_error ( errno , " WARNING! Content file '%s' not found, attempting with another copy... \n " , path ) ;
2013-08-25 22:39:05 +02:00
/* ensure to rewrite all the content files */
state - > need_write = 1 ;
2013-08-17 16:17:53 +02:00
}
}
2013-08-25 22:39:05 +02:00
/* next content file */
2013-08-17 16:17:53 +02:00
node = node - > next ;
}
/* if not found, assume empty */
if ( ! f ) {
2026-07-29 16:42:56 +02:00
log_info ( EUSER , " No content file found. Assuming empty. \n " ) ;
2013-11-26 18:43:12 +01:00
2013-08-17 16:17:53 +02:00
/* create the initial mapping */
state_map ( state ) ;
return ;
}
2013-08-25 22:39:05 +02:00
/* get the stat of the content file */
2013-08-17 16:17:53 +02:00
ret = fstat ( shandle ( f ) , & st ) ;
if ( ret ! = 0 ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error stating the content file '%s'. %s. \n " , path , strerror ( errno ) ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-04-09 15:15:48 +02:00
if ( ! state - > no_conf ) {
2026-05-29 11:01:44 +02:00
log_tag ( " content:%s \n " , esc_tag ( path ) ) ;
2026-04-09 15:15:48 +02:00
log_tag ( " content_info:read_unixtime:% " PRId64 " \n " , ( int64_t ) st . st_mtime ) ;
log_flush ( ) ;
}
2013-08-26 20:33:25 +02:00
/* go further to check other content files */
2013-08-25 22:39:05 +02:00
while ( node ) {
char other_path [ PATH_MAX ] ;
struct stat other_st ;
struct snapraid_content * content = node - > data ;
pathcpy ( other_path , sizeof ( other_path ) , content - > content ) ;
ret = stat ( other_path , & other_st ) ;
if ( ret ! = 0 ) {
/* allow missing content files, but not any other kind of error */
if ( errno ! = ENOENT ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error stating the content file '%s'. %s. \n " , other_path , strerror ( errno ) ) ;
2013-08-25 22:39:05 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-25 22:39:05 +02:00
}
/* ensure to rewrite all the content files */
state - > need_write = 1 ;
} else {
2026-09-07 10:56:48 +02:00
/* different size or modification time means that the replicas are not from the same publication */
if ( other_st . st_size ! = st . st_size | | other_st . st_mtime ! = st . st_mtime ) {
log_error ( ECONTENT , " WARNING! Content files '%s' and '%s' have a different size or modification time! \n " , path , other_path ) ;
log_error ( ECONTENT , " Likely the content replicas are from different publications or one of them is broken! \n " ) ;
2014-09-27 00:32:39 +02:00
2013-08-25 22:39:05 +02:00
/* ensure to rewrite all the content files */
state - > need_write = 1 ;
}
}
/* next content file */
node = node - > next ;
}
2026-04-27 15:43:06 +02:00
/*
* Start with a undefined default .
* it ' s for compatibility with version 1.0 where MD5 was implicit .
*/
2013-08-17 16:17:53 +02:00
state - > hash = HASH_UNDEFINED ;
/* start with a zero seed, it was the default in old versions */
2016-04-07 21:39:02 +02:00
memset ( state - > hashseed , 0 , HASH_MAX ) ;
2013-08-17 16:17:53 +02:00
/* previous hash, start with an undefined value */
state - > prevhash = HASH_UNDEFINED ;
2013-09-26 20:34:01 +02:00
/* intentionally not set the prevhashseed, if used valgrind will warn about it */
2013-08-17 16:17:53 +02:00
/* get the first char to detect the file type */
c = sgetc ( f ) ;
sungetc ( c , f ) ;
2013-08-19 11:19:26 +02:00
/* guess the file type from the first char */
2013-08-17 16:17:53 +02:00
if ( c = = ' S ' ) {
2026-09-09 22:31:47 +02:00
/* parsing is the only user of the extent trees until the content is loaded */
fs_single_thread ( state , 1 ) ;
2015-10-28 22:04:04 +01:00
state_read_content ( state , path , f ) ;
2026-09-09 22:31:47 +02:00
fs_single_thread ( state , 0 ) ;
2013-08-17 16:17:53 +02:00
} else {
2015-10-01 23:01:54 +02:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " From SnapRAID v9.0 the text content file is not supported anymore. \n " ) ;
log_fatal ( EUSER , " You have first to upgrade to SnapRAID v8.1 to convert it to binary format. \n " ) ;
2015-10-01 23:01:54 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
sclose ( f ) ;
if ( state - > hash = = HASH_UNDEFINED ) {
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EUSER , " The checksum to use is not specified. \n " ) ;
log_fatal ( EUSER , " This happens because you are likely upgrading from SnapRAID 1.0. \n " ) ;
log_fatal ( EUSER , " To use a new SnapRAID you must restart from scratch, \n " ) ;
log_fatal ( EUSER , " deleting all the content and parity files. \n " ) ;
2013-08-17 16:17:53 +02:00
exit ( EXIT_FAILURE ) ;
2014-02-03 22:40:51 +01:00
/* LCOV_EXCL_STOP */
2013-08-17 16:17:53 +02:00
}
2026-04-26 15:59:58 +02:00
if ( state - > unsynced_blocks )
2026-04-28 00:35:15 +02:00
msg_progress ( " WARNING! The latest sync was interrupted! \n " ) ;
2026-04-26 15:59:58 +02:00
2013-08-17 16:17:53 +02:00
/* update the mapping */
state_map ( state ) ;
state_content_check ( state , path ) ;
2014-06-13 18:23:24 +02:00
/* mark that we read the content file, and it passed all the checks */
state - > checked_read = 1 ;
2013-08-17 16:17:53 +02:00
}
2015-10-29 23:37:58 +01:00
struct state_verify_thread_context {
struct snapraid_state * state ;
struct snapraid_content * content ;
2016-01-01 17:42:37 +01:00
# if HAVE_MT_VERIFY
2021-05-25 22:08:12 +02:00
thread_id_t thread ;
2015-10-29 23:37:58 +01:00
# else
void * retval ;
# endif
/* input */
uint32_t crc ;
2016-01-01 11:05:11 +01:00
STREAM * f ;
2015-10-29 23:37:58 +01:00
} ;
2015-10-28 22:04:04 +01:00
static void * state_verify_thread ( void * arg )
{
2015-10-29 23:37:58 +01:00
struct state_verify_thread_context * context = arg ;
2019-11-16 08:41:43 +01:00
struct snapraid_content * content = context - > content ;
2016-01-01 11:05:11 +01:00
STREAM * f = context - > f ;
2015-10-28 22:04:04 +01:00
unsigned char buf [ 4 ] ;
uint32_t crc_stored ;
uint32_t crc_computed ;
2019-11-16 08:41:43 +01:00
uint64_t start ;
2026-03-02 01:13:19 +01:00
start = os_tick_ms ( ) ;
2015-10-28 22:04:04 +01:00
if ( sdeplete ( f , buf ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Failed to flush content file '%s'. %s. \n " , serrorfile ( f ) , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
return context ;
2015-10-28 22:04:04 +01:00
/* LCOV_EXCL_STOP */
}
/* get the stored crc from the last four bytes */
crc_stored = buf [ 0 ] | ( uint32_t ) buf [ 1 ] < < 8 | ( uint32_t ) buf [ 2 ] < < 16 | ( uint32_t ) buf [ 3 ] < < 24 ;
2015-10-29 23:37:58 +01:00
if ( crc_stored ! = context - > crc ) {
2015-10-28 22:04:04 +01:00
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " DANGER! Wrong stored CRC in '%s' \n " , serrorfile ( f ) ) ;
2016-01-01 11:05:11 +01:00
return context ;
2015-10-28 22:04:04 +01:00
/* LCOV_EXCL_STOP */
}
/* get the computed crc */
crc_computed = scrc ( f ) ;
/* adjust the stored crc to include itself */
crc_stored = crc32c ( crc_stored , buf , 4 ) ;
if ( crc_computed ! = crc_stored ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( ECONTENT , " DANGER! Wrong file CRC in '%s' \n " , serrorfile ( f ) ) ;
2016-01-01 11:05:11 +01:00
return context ;
2015-10-28 22:04:04 +01:00
/* LCOV_EXCL_STOP */
}
2026-03-02 01:13:19 +01:00
msg_progress ( " Verified %s in % " PRIu64 " seconds \n " , content - > content , ( os_tick_ms ( ) - start ) / 1000 ) ;
2019-11-16 08:41:43 +01:00
2015-10-28 22:04:04 +01:00
return 0 ;
}
2015-10-29 23:37:58 +01:00
static void state_verify_content ( struct snapraid_state * state , uint32_t crc )
2015-10-28 22:04:04 +01:00
{
tommy_node * i ;
int fail ;
2021-11-29 17:37:23 +01:00
msg_progress ( " Verifying... \n " ) ;
2015-10-28 22:04:04 +01:00
/* start all reading threads */
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
2015-10-29 23:37:58 +01:00
struct state_verify_thread_context * context ;
2016-01-01 11:05:11 +01:00
char tmp [ PATH_MAX ] ;
STREAM * f ;
pathprint ( tmp , sizeof ( tmp ) , " %s.tmp " , content - > content ) ;
2026-09-09 20:39:33 +02:00
f = sopen_read ( tmp , ADVISE_SEQUENTIAL | STREAM_FLAGS_CRC ) ;
2016-01-01 11:05:11 +01:00
if ( f = = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error reopening the temporary content file '%s'. %s. \n " , tmp , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-10-28 22:04:04 +01:00
2015-10-29 23:37:58 +01:00
/* allocate the thread context */
context = malloc_nofail ( sizeof ( struct state_verify_thread_context ) ) ;
content - > context = context ;
2015-10-28 22:04:04 +01:00
2015-10-29 23:37:58 +01:00
/* initialize */
context - > state = state ;
context - > content = content ;
context - > crc = crc ;
2016-01-01 11:05:11 +01:00
context - > f = f ;
2015-10-28 22:04:04 +01:00
2016-01-01 17:42:37 +01:00
# if HAVE_MT_VERIFY
2021-05-25 22:08:12 +02:00
thread_create ( & context - > thread , state_verify_thread , context ) ;
2015-10-28 22:04:04 +01:00
# else
2016-01-01 11:05:11 +01:00
context - > retval = state_verify_thread ( context ) ;
2015-10-28 22:04:04 +01:00
# endif
i = i - > next ;
}
/* join all thread */
fail = 0 ;
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
2015-10-29 23:37:58 +01:00
struct state_verify_thread_context * context = content - > context ;
2015-10-28 22:04:04 +01:00
void * retval ;
2016-01-01 17:42:37 +01:00
# if HAVE_MT_VERIFY
2016-02-13 15:35:23 +01:00
thread_join ( context - > thread , & retval ) ;
2015-10-28 22:04:04 +01:00
# else
2015-10-29 23:37:58 +01:00
retval = context - > retval ;
2015-10-28 22:04:04 +01:00
# endif
2015-10-31 16:29:08 +01:00
if ( retval ) {
/* LCOV_EXCL_START */
2015-10-28 22:04:04 +01:00
fail = 1 ;
2015-10-31 16:29:08 +01:00
/* LCOV_EXCL_STOP */
2016-01-01 11:05:11 +01:00
} else {
STREAM * f = context - > f ;
if ( sclose ( f ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error closing the content file. %s. \n " , strerror ( errno ) ) ;
2016-01-01 11:05:11 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2015-10-31 16:29:08 +01:00
}
2015-10-28 22:04:04 +01:00
2015-10-29 23:37:58 +01:00
free ( context ) ;
2015-10-28 22:04:04 +01:00
i = i - > next ;
}
/* abort on failure */
if ( fail ) {
/* LCOV_EXCL_START */
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
}
2015-10-29 23:37:58 +01:00
static void state_rename_content ( struct snapraid_state * state )
2015-10-28 22:04:04 +01:00
{
tommy_node * i ;
2026-08-18 23:04:56 +02:00
# ifndef _WIN32
/*
* This sequence relies on POSIX file - system semantics .
*
2026-09-05 09:51:38 +02:00
* The temporary content file has already been flushed and fsync ' ed in
* ssync ( ) ( using F_FULLFSYNC on macOS to force a physical media flush ) .
2026-08-18 23:04:56 +02:00
* rename ( ) / renameat ( ) atomically replaces the old content file , but the
* directory entry update is not necessarily durable when the rename
* returns .
*
* Open and fsync the parent directory after the rename to ensure that the
* new content file name is persisted before state_write ( ) returns . This
* ordering is required for crash consistency because parity modifications
* may start only after the pre - sync content state has been durably
* published .
*
2026-09-05 09:51:38 +02:00
* On macOS , standard fsync ( ) only pushes data from kernel buffers to the
* drive ' s volatile cache without requesting a physical media flush . F_FULLFSYNC
* forces the drive controller to commit all cached data to permanent storage .
* Because F_FULLFSYNC is not supported on directory descriptors , the subsequent
* directory fsync ( ) only flushes to the drive cache and is not guaranteed to be
* instantly persistent across power loss . However , major database engines such
* as SQLite and PostgreSQL follow this exact same pattern on Darwin , making it
* the standard industry practice .
*
* See :
* https : //github.com/sqlite/sqlite/blob/f3b9f74d81132426dee1ccc07a67fdad2ccfeaa9/src/os_unix.c#L3928-L3953
* https : //github.com/postgres/postgres/blob/74c77052bc20017076100f0649ca0f44b1b98c29/src/backend/storage/file/fd.c#L835-L852
*
2026-08-18 23:04:56 +02:00
* When renameat ( ) is available , perform the rename relative to the same
* directory descriptor that is subsequently fsync ' ed . Use O_DIRECTORY
* when available , while retaining compatibility with POSIX systems where
* O_DIRECTORY is not defined .
*/
2018-06-04 22:14:23 +02:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
char tmp [ PATH_MAX ] ;
char dir [ PATH_MAX ] ;
char * slash ;
int handle ;
pathcpy ( dir , sizeof ( dir ) , content - > content ) ;
2026-05-20 00:23:43 +02:00
slash = strrchr ( dir , ' / ' ) ;
2018-06-04 22:14:23 +02:00
if ( slash )
2026-05-20 00:23:43 +02:00
slash [ 1 ] = 0 ;
2018-06-04 22:14:23 +02:00
else
pathcpy ( dir , sizeof ( dir ) , " . " ) ;
2026-08-18 23:04:56 +02:00
int flags = O_RDONLY ;
# ifdef O_DIRECTORY
flags | = O_DIRECTORY ;
# endif
2018-06-04 22:14:23 +02:00
/* open the directory to get the handle */
2026-08-18 23:04:56 +02:00
handle = open ( dir , flags ) ;
2018-06-04 22:14:23 +02:00
if ( handle < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error opening the directory '%s'. %s. \n " , dir , strerror ( errno ) ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
/* now rename the just written copy with the correct name */
pathprint ( tmp , sizeof ( tmp ) , " %s.tmp " , content - > content ) ;
2026-08-18 23:04:56 +02:00
# if HAVE_RENAMEAT
const char * content_name = slash ? content - > content + ( slash - dir ) + 1 : content - > content ;
char tmp_name [ PATH_MAX ] ;
pathprint ( tmp_name , sizeof ( tmp_name ) , " %s.tmp " , content_name ) ;
if ( renameat ( handle , tmp_name , handle , content_name ) ! = 0 ) {
/* LCOV_EXCL_START */
log_fatal ( errno , " Error renaming the content file '%s' to '%s'. %s. \n " , tmp , content - > content , strerror ( errno ) ) ;
close ( handle ) ;
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
# else
2018-06-04 22:14:23 +02:00
if ( rename ( tmp , content - > content ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error renaming the content file '%s' to '%s'. %s. \n " , tmp , content - > content , strerror ( errno ) ) ;
2026-08-18 23:04:56 +02:00
close ( handle ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
2026-08-18 23:04:56 +02:00
# endif
2018-06-04 22:14:23 +02:00
/* sync the directory */
if ( fsync ( handle ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error syncing the directory '%s'. %s. \n " , dir , strerror ( errno ) ) ;
2026-08-18 23:04:56 +02:00
close ( handle ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
if ( close ( handle ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error closing the directory '%s'. %s. \n " , dir , strerror ( errno ) ) ;
2018-06-04 22:14:23 +02:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
i = i - > next ;
}
# else
2015-10-28 22:04:04 +01:00
i = tommy_list_head ( & state - > contentlist ) ;
while ( i ) {
struct snapraid_content * content = i - > data ;
char tmp [ PATH_MAX ] ;
/* now renames the just written copy with the correct name */
pathprint ( tmp , sizeof ( tmp ) , " %s.tmp " , content - > content ) ;
if ( rename ( tmp , content - > content ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " Error renaming the content file '%s' to '%s' in rename(). %s. \n " , tmp , content - > content , strerror ( errno ) ) ;
2015-10-28 22:04:04 +01:00
exit ( EXIT_FAILURE ) ;
/* LCOV_EXCL_STOP */
}
i = i - > next ;
}
2018-06-04 22:14:23 +02:00
# endif
2015-10-28 22:04:04 +01:00
}
2026-08-29 10:07:40 +02:00
/*
* Check if a modification time nanosecond value can be represented in the
* content format .
*
* STAT_NSEC_INVALID is encoded as 0. Valid nanoseconds in the range
* 0. .999999999 are encoded as 1. .1000000000 .
*/
static int state_write_mtime_nsec_valid ( int mtime_nsec )
{
return mtime_nsec = = STAT_NSEC_INVALID | | ( mtime_nsec > = 0 & & mtime_nsec < 1000000000 ) ;
}
/*
* Validate invariants required by the persistent content format .
*
* state_write ( ) must never serialize a state that state_read ( ) would reject .
* In particular , some internal values use signed types or sentinel values but
* are stored in the content file using unsigned encodings . If such values are
* outside their valid domain , serializing them may produce a syntactically
* valid content file that cannot be read back .
*
* Keep this check limited to serialization invariants . More complex consistency
* checks on the filesystem and block mappings are performed separately by
* state_fscheck ( ) .
*/
static void state_write_check ( struct snapraid_state * state )
{
tommy_node * i ;
unsigned l ;
unsigned s ;
block_off_t blockmax ;
if ( state - > block_size = = 0 ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Zero block size before state write! \n " ) ;
os_abort ( ) ;
}
if ( state - > level = = 0 | | state - > level > LEV_MAX ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid parity level count %u! \n " , state - > level ) ;
os_abort ( ) ;
}
blockmax = parity_allocated_size ( state ) ;
if ( blockmax > BLOCK_MAX ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Parity size % " PRIu64 " is too large! \n " , blockmax ) ;
os_abort ( ) ;
}
for ( l = 0 ; l < state - > level ; + + l ) {
if ( state - > parity [ l ] . split_mac = = 0 | | state - > parity [ l ] . split_mac > SPLIT_MAX ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid parity split count %u! \n " , state - > parity [ l ] . split_mac ) ;
os_abort ( ) ;
}
for ( s = 0 ; s < state - > parity [ l ] . split_mac ; + + s ) {
if ( state - > parity [ l ] . split_map [ s ] . size < 0 ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid parity split size % " PRIi64 " ! \n " , state - > parity [ l ] . split_map [ s ] . size ) ;
os_abort ( ) ;
}
}
}
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
tommy_node * j ;
for ( j = disk - > filelist ; j ! = 0 ; j = j - > next ) {
struct snapraid_file * file = j - > data ;
if ( file - > size < 0 ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid file size % " PRIi64 " ! \n " , file - > size ) ;
os_abort ( ) ;
}
if ( ! state_write_mtime_nsec_valid ( file - > mtime_nsec ) ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid file mtime nanoseconds %d! \n " , file - > mtime_nsec ) ;
os_abort ( ) ;
}
}
for ( j = disk - > dealloclist ; j ! = 0 ; j = j - > next ) {
struct snapraid_dealloc * dealloc = j - > data ;
if ( dealloc - > size < 0 ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid deallocated file size % " PRIi64 " ! \n " , dealloc - > size ) ;
os_abort ( ) ;
}
if ( ! state_write_mtime_nsec_valid ( dealloc - > mtime_nsec ) ) {
log_fatal ( EINTERNAL , " Internal inconsistency: Invalid deallocated file mtime nanoseconds %d! \n " , dealloc - > mtime_nsec ) ;
os_abort ( ) ;
}
}
}
}
2013-08-17 16:17:53 +02:00
void state_write ( struct snapraid_state * state )
{
2015-10-28 22:04:04 +01:00
uint32_t crc ;
2026-04-09 15:15:48 +02:00
time_t now ;
2013-08-17 16:17:53 +02:00
2026-08-29 10:07:40 +02:00
/* verify that all persistent values are serializable */
state_write_check ( state ) ;
2015-10-29 23:37:58 +01:00
/* write all the content files */
state_write_content ( state , & crc ) ;
2013-08-17 16:17:53 +02:00
2015-10-29 23:37:58 +01:00
/* verify the just written files */
state_verify_content ( state , crc ) ;
2014-06-13 18:23:24 +02:00
2015-10-29 23:37:58 +01:00
/* rename the new files, over the old ones */
state_rename_content ( state ) ;
2011-09-03 22:21:45 +02:00
2026-04-09 15:15:48 +02:00
/* log the write time of the content file */
now = time ( 0 ) ;
log_tag ( " content_info:write_unixtime:% " PRId64 " \n " , ( int64_t ) now ) ;
log_flush ( ) ;
2011-09-03 22:21:45 +02:00
state - > need_write = 0 ; /* no write needed anymore */
2014-06-13 18:23:24 +02:00
state - > checked_read = 0 ; /* what we wrote is not checked in read */
2025-10-13 00:20:36 +02:00
state - > written = 1 ;
2011-03-04 20:59:21 +01:00
}
2026-04-27 14:39:38 +02:00
void state_commit ( struct snapraid_state * state )
{
tommy_node * i ;
/* for each disk */
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
2026-08-14 01:18:03 +02:00
if ( ! tommy_list_empty ( & disk - > dealloclist ) ) {
/* clear the dealloc list */
2026-08-24 23:03:00 +02:00
tommy_list_foreach ( & disk - > dealloclist , dealloc_free ) ;
2026-08-14 01:18:03 +02:00
tommy_list_init ( & disk - > dealloclist ) ;
/* mark the state as needing write */
state - > need_write = 1 ;
}
2026-04-27 14:39:38 +02:00
}
}
2015-10-16 23:17:38 +02:00
void state_skip ( struct snapraid_state * state )
{
tommy_node * i ;
/* for each disk */
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
tommy_node * j ;
struct snapraid_disk * disk = i - > data ;
if ( ! disk - > skip_access )
continue ;
/* for each file */
for ( j = tommy_list_head ( & disk - > filelist ) ; j ! = 0 ; j = j - > next ) {
struct snapraid_file * file = j - > data ;
file_flag_set ( file , FILE_IS_EXCLUDED ) ;
}
/* for each link */
for ( j = tommy_list_head ( & disk - > linklist ) ; j ! = 0 ; j = j - > next ) {
struct snapraid_link * slink = j - > data ;
link_flag_set ( slink , FILE_IS_EXCLUDED ) ;
}
/* for each dir */
for ( j = tommy_list_head ( & disk - > dirlist ) ; j ! = 0 ; j = j - > next ) {
struct snapraid_dir * dir = j - > data ;
dir_flag_set ( dir , FILE_IS_EXCLUDED ) ;
}
}
}
2013-08-18 19:24:55 +02:00
void state_filter ( struct snapraid_state * state , tommy_list * filterlist_file , tommy_list * filterlist_disk , int filter_missing , int filter_error )
2011-03-30 00:14:43 +02:00
{
2011-09-23 21:48:50 +02:00
tommy_node * i ;
2015-10-17 17:36:40 +02:00
unsigned l ;
2011-03-30 00:14:43 +02:00
2011-04-26 18:51:42 +02:00
/* if no filter, include all */
2013-08-18 19:24:55 +02:00
if ( ! filter_missing & & ! filter_error & & tommy_list_empty ( filterlist_file ) & & tommy_list_empty ( filterlist_disk ) )
2011-03-30 00:14:43 +02:00
return ;
2021-05-22 08:46:53 +02:00
msg_progress ( " Selecting... \n " ) ;
2011-03-30 00:14:43 +02:00
2015-02-22 14:31:03 +01:00
for ( i = tommy_list_head ( filterlist_disk ) ; i ! = 0 ; i = i - > next ) {
struct snapraid_filter * filter = i - > data ;
msg_verbose ( " \t %s%s \n " , filter - > pattern , filter - > is_disk ? " // " : " " ) ;
2011-03-30 00:14:43 +02:00
}
2015-02-22 14:31:03 +01:00
for ( i = tommy_list_head ( filterlist_file ) ; i ! = 0 ; i = i - > next ) {
struct snapraid_filter * filter = i - > data ;
msg_verbose ( " \t %s%s \n " , filter - > pattern , filter - > is_dir ? " / " : " " ) ;
}
if ( filter_missing )
msg_verbose ( " \t <missing> \n " ) ;
if ( filter_error )
msg_verbose ( " \t <error> \n " ) ;
2011-03-30 00:14:43 +02:00
/* for each disk */
2014-09-27 00:32:39 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
2011-03-30 00:14:43 +02:00
tommy_node * j ;
2011-09-23 21:48:50 +02:00
struct snapraid_disk * disk = i - > data ;
2011-03-30 00:14:43 +02:00
2013-03-01 18:29:04 +01:00
/* if we filter for presence, we have to access the disk, so better to print something */
if ( filter_missing )
2015-02-22 14:31:03 +01:00
msg_progress ( " Scanning disk %s... \n " , disk - > name ) ;
2013-03-01 18:29:04 +01:00
2011-03-30 00:14:43 +02:00
/* for each file */
2014-09-27 00:32:39 +02:00
for ( j = tommy_list_head ( & disk - > filelist ) ; j ! = 0 ; j = j - > next ) {
2011-03-30 00:14:43 +02:00
struct snapraid_file * file = j - > data ;
2015-01-27 23:10:38 +01:00
if ( filter_path ( filterlist_disk , 0 , disk - > name , file - > sub ) ! = 0
| | filter_path ( filterlist_file , 0 , disk - > name , file - > sub ) ! = 0
2013-03-01 18:29:04 +01:00
| | filter_existence ( filter_missing , disk - > dir , file - > sub ) ! = 0
2015-10-01 18:19:48 +02:00
| | filter_correctness ( filter_error , & state - > infoarr , disk , file ) ! = 0
2012-11-23 00:09:50 +01:00
) {
2011-04-28 23:09:19 +02:00
file_flag_set ( file , FILE_IS_EXCLUDED ) ;
}
2011-03-30 00:14:43 +02:00
}
2011-08-26 14:18:38 +02:00
/* for each link */
2014-09-27 00:32:39 +02:00
for ( j = tommy_list_head ( & disk - > linklist ) ; j ! = 0 ; j = j - > next ) {
2015-10-08 22:19:52 +02:00
struct snapraid_link * slink = j - > data ;
2011-08-26 14:18:38 +02:00
2015-10-08 22:19:52 +02:00
if ( filter_path ( filterlist_disk , 0 , disk - > name , slink - > sub ) ! = 0
| | filter_path ( filterlist_file , 0 , disk - > name , slink - > sub ) ! = 0
| | filter_existence ( filter_missing , disk - > dir , slink - > sub ) ! = 0
2012-11-23 00:09:50 +01:00
) {
2015-10-08 22:19:52 +02:00
link_flag_set ( slink , FILE_IS_EXCLUDED ) ;
2011-08-26 14:18:38 +02:00
}
}
2013-01-27 16:44:04 +01:00
2016-05-14 21:59:33 +02:00
/* for each empty dir */
2014-09-27 00:32:39 +02:00
for ( j = tommy_list_head ( & disk - > dirlist ) ; j ! = 0 ; j = j - > next ) {
2013-01-27 16:44:04 +01:00
struct snapraid_dir * dir = j - > data ;
2016-05-14 21:59:33 +02:00
if ( filter_emptydir ( filterlist_disk , 0 , disk - > name , dir - > sub ) ! = 0
| | filter_emptydir ( filterlist_file , 0 , disk - > name , dir - > sub ) ! = 0
2013-03-01 18:29:04 +01:00
| | filter_existence ( filter_missing , disk - > dir , dir - > sub ) ! = 0
2013-01-27 16:44:04 +01:00
) {
dir_flag_set ( dir , FILE_IS_EXCLUDED ) ;
}
}
2011-03-30 00:14:43 +02:00
}
2015-10-17 17:36:40 +02:00
2020-05-02 14:21:40 +02:00
/* if we are filtering by disk, exclude any parity not explicitly included */
2015-10-17 17:36:40 +02:00
if ( ! tommy_list_empty ( filterlist_disk ) ) {
/* for each parity disk */
for ( l = 0 ; l < state - > level ; + + l ) {
/* check if the parity is excluded by name */
if ( filter_path ( filterlist_disk , 0 , lev_config_name ( l ) , 0 ) ! = 0 ) {
/* excluded the parity from further operation */
2017-03-26 18:10:46 +02:00
state - > parity [ l ] . is_excluded_by_filter = 1 ;
2015-10-17 17:36:40 +02:00
}
}
} else {
/* if we are filtering by file, exclude all parity */
if ( filter_missing | | ! tommy_list_empty ( filterlist_file ) ) {
/* for each parity disk */
for ( l = 0 ; l < state - > level ; + + l ) {
2017-03-26 18:10:46 +02:00
state - > parity [ l ] . is_excluded_by_filter = 1 ;
2015-10-17 17:36:40 +02:00
}
}
}
2011-03-30 00:14:43 +02:00
}
2013-07-10 19:37:58 +02:00
int state_progress_begin ( struct snapraid_state * state , block_off_t blockstart , block_off_t blockmax , block_off_t countmax )
2011-06-20 20:35:04 +02:00
{
2014-10-21 22:10:54 +02:00
time_t now ;
2013-06-29 18:11:36 +02:00
if ( state - > opt . gui ) {
2026-06-03 23:06:27 +02:00
log_tag ( " run:begin:% " PRIu64 " :% " PRIu64 " :% " PRIu64 " \n " , blockstart , blockmax , countmax ) ;
2015-04-20 22:07:42 +02:00
log_flush ( ) ;
2014-10-21 22:10:54 +02:00
}
2011-06-20 20:35:04 +02:00
2014-10-21 22:10:54 +02:00
now = time ( 0 ) ;
2011-06-20 20:35:04 +02:00
2014-10-21 22:10:54 +02:00
state - > progress_whole_start = now ;
state - > progress_tick = 0 ;
state - > progress_ptr = 0 ;
state - > progress_wasted = 0 ;
2013-07-10 19:37:58 +02:00
2025-09-21 19:54:10 +02:00
/* start of thermal control */
if ( ! state_thermal_begin ( state , now ) )
2026-02-09 23:49:02 +01:00
return - 1 ;
2025-09-21 19:54:10 +02:00
2013-07-10 19:37:58 +02:00
/* stop if requested */
2026-06-24 00:15:15 +02:00
if ( os_signal_interrupt ( ) ) {
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_START */
2013-07-10 19:37:58 +02:00
if ( ! state - > opt . gui ) {
2026-01-17 21:16:21 +01:00
msg_status ( " Not starting due to interruption \n " ) ;
2013-07-10 19:37:58 +02:00
}
2015-04-20 22:07:42 +02:00
log_tag ( " sigint:0: SIGINT received \n " ) ;
log_flush ( ) ;
2026-02-09 23:49:02 +01:00
return 1 ;
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_STOP */
2013-07-10 19:37:58 +02:00
}
2026-02-09 23:49:02 +01:00
return 0 ;
2011-06-20 20:35:04 +02:00
}
2025-10-13 11:46:36 +02:00
void state_progress_end ( struct snapraid_state * state , block_off_t countpos , block_off_t countmax , data_off_t countsize , const char * msg )
2011-06-20 20:35:04 +02:00
{
2013-06-29 18:11:36 +02:00
if ( state - > opt . gui ) {
2015-04-20 22:07:42 +02:00
log_tag ( " run:end \n " ) ;
log_flush ( ) ;
2015-02-04 08:10:39 +01:00
} else if ( countmax = = 0 ) {
2025-10-13 00:20:36 +02:00
if ( state - > need_write | | state - > written ) {
msg_status ( " 100%% completed \n " ) ;
} else {
2025-10-13 11:46:36 +02:00
msg_status ( " %s " , msg ) ;
2025-10-13 00:20:36 +02:00
}
2015-02-22 14:31:03 +01:00
} else {
2013-11-16 16:21:41 +01:00
time_t now ;
time_t elapsed ;
2026-06-04 00:34:51 +02:00
data_off_t countsize_MB = ( countsize + MEGA - 1 ) / MEGA ;
2011-06-20 20:35:04 +02:00
2013-11-16 16:21:41 +01:00
now = time ( 0 ) ;
2014-09-09 22:15:50 +02:00
elapsed = now - state - > progress_whole_start - state - > progress_wasted ;
2013-11-16 16:21:41 +01:00
2026-06-04 00:34:51 +02:00
msg_bar ( " %u%% completed, % " PRIu64 " MB accessed " , muldiv ( countpos , 100 , countmax ) , countsize_MB ) ;
2016-12-22 15:51:25 +01:00
msg_bar ( " in %u:%02u " , ( unsigned ) ( elapsed / 3600 ) , ( unsigned ) ( ( elapsed % 3600 ) / 60 ) ) ;
2015-02-22 14:31:03 +01:00
msg_bar ( " \n " ) ;
2015-03-23 22:57:36 +01:00
msg_flush ( ) ;
2011-06-20 20:35:04 +02:00
}
}
2012-03-13 23:59:59 +01:00
void state_progress_stop ( struct snapraid_state * state )
{
time_t now ;
now = time ( 0 ) ;
state - > progress_interruption = now ;
}
void state_progress_restart ( struct snapraid_state * state )
{
time_t now ;
now = time ( 0 ) ;
2014-09-09 22:15:50 +02:00
/* reset the progress counter */
state - > progress_tick = 0 ;
state - > progress_ptr = 0 ;
2012-03-13 23:59:59 +01:00
if ( now > = state - > progress_interruption ) /* avoid degenerated cases when the clock is manually adjusted */
2014-09-09 22:15:50 +02:00
state - > progress_wasted + = now - state - > progress_interruption ;
2012-03-13 23:59:59 +01:00
}
2016-01-27 21:08:59 +01:00
/**
2026-03-02 01:13:19 +01:00
* Set the latest os_tick data in the progress vector .
2016-01-27 21:08:59 +01:00
*/
static void state_progress_latest ( struct snapraid_state * state )
{
tommy_node * i ;
unsigned l ;
state - > progress_tick_misc [ state - > progress_ptr ] = state - > tick_misc ;
2016-01-31 16:38:29 +01:00
state - > progress_tick_sched [ state - > progress_ptr ] = state - > tick_sched ;
2016-01-27 21:08:59 +01:00
state - > progress_tick_raid [ state - > progress_ptr ] = state - > tick_raid ;
state - > progress_tick_hash [ state - > progress_ptr ] = state - > tick_hash ;
state - > progress_tick_io [ state - > progress_ptr ] = state - > tick_io ;
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
disk - > progress_tick [ state - > progress_ptr ] = disk - > tick ;
}
for ( l = 0 ; l < state - > level ; + + l )
state - > parity [ l ] . progress_tick [ state - > progress_ptr ] = state - > parity [ l ] . tick ;
}
/**
* Number of columns
*/
2025-09-21 19:54:10 +02:00
# define GRAPH_COLUMN 78
2016-01-27 21:08:59 +01:00
/**
* Get the reference value , 0 if index is PROGRESS_MAX
*/
2016-04-10 22:44:40 +02:00
# define ref(map, index) (index < PROGRESS_MAX ? map[index] : 0)
2016-01-27 21:08:59 +01:00
2025-09-21 19:54:10 +02:00
static struct snapraid_thermal * state_thermal_find ( struct snapraid_state * state , const char * name )
{
tommy_node * i ;
struct snapraid_thermal * found = 0 ;
for ( i = tommy_list_head ( & state - > thermallist ) ; i ! = 0 ; i = i - > next ) {
struct snapraid_thermal * thermal = i - > data ;
if ( strcmp ( thermal - > name , name ) = = 0 ) {
2026-01-05 15:55:38 +01:00
if ( found = = 0
/* if multiple matches, return the one with highest temperature */
2025-09-21 19:54:10 +02:00
| | found - > latest_temperature < thermal - > latest_temperature
2026-01-05 15:55:38 +01:00
/* or, if the temperature is equal, the one with the higher r_squared */
2025-09-21 19:54:10 +02:00
| | ( found - > latest_temperature = = thermal - > latest_temperature & & found - > params . r_squared < thermal - > params . r_squared )
) {
found = thermal ;
}
}
}
return found ;
}
# define THERMAL_PAD " "
2016-01-27 21:08:59 +01:00
static void state_progress_graph ( struct snapraid_state * state , struct snapraid_io * io , unsigned current , unsigned oldest )
{
uint64_t v ;
uint64_t tick_total ;
2026-06-04 00:34:51 +02:00
size_t bar ;
2016-01-27 21:08:59 +01:00
tommy_node * i ;
unsigned l ;
size_t pad ;
2025-09-21 19:54:10 +02:00
size_t pre ;
2016-01-27 21:08:59 +01:00
tick_total = 0 ;
tick_total + = state - > progress_tick_misc [ current ] - ref ( state - > progress_tick_misc , oldest ) ;
2016-01-31 16:38:29 +01:00
tick_total + = state - > progress_tick_sched [ current ] - ref ( state - > progress_tick_sched , oldest ) ;
2016-01-27 21:08:59 +01:00
tick_total + = state - > progress_tick_raid [ current ] - ref ( state - > progress_tick_raid , oldest ) ;
tick_total + = state - > progress_tick_hash [ current ] - ref ( state - > progress_tick_hash , oldest ) ;
tick_total + = state - > progress_tick_io [ current ] - ref ( state - > progress_tick_io , oldest ) ;
if ( ! tick_total )
return ;
pad = 4 ;
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
size_t len = strlen ( disk - > name ) ;
if ( pad < len )
pad = len ;
}
for ( l = 0 ; l < state - > level ; + + l ) {
size_t len = strlen ( lev_config_name ( l ) ) ;
if ( pad < len )
pad = len ;
}
/* extra space */
pad + = 1 ;
2025-09-21 19:54:10 +02:00
pre = 4 ;
if ( pad + pre + 30 < GRAPH_COLUMN )
bar = GRAPH_COLUMN - pad - pre ;
2016-01-27 21:08:59 +01:00
else
2025-09-21 19:54:10 +02:00
bar = 30 ; /* at least a bar of 30 */
2016-01-27 21:08:59 +01:00
if ( io ) {
2016-02-05 20:20:15 +01:00
const char * legend = " cached blocks (instant, more is better) " ;
2016-01-27 21:08:59 +01:00
/* refresh the cached blocks info */
io_refresh ( io ) ;
printf ( " \n " ) ;
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
2017-11-19 19:33:47 +01:00
v = disk - > cached_blocks ;
2016-01-27 21:08:59 +01:00
printr ( disk - > name , pad ) ;
printf ( " %4 " PRIu64 " | " , v ) ;
2017-11-19 19:33:47 +01:00
if ( disk - > progress_file & & disk - > progress_file - > sub )
printf ( " %s " , disk - > progress_file - > sub ) ;
else
printf ( " - " ) ;
2016-01-27 21:08:59 +01:00
printf ( " \n " ) ;
}
for ( l = 0 ; l < state - > level ; + + l ) {
2017-11-19 19:33:47 +01:00
v = state - > parity [ l ] . cached_blocks ;
2016-01-27 21:08:59 +01:00
printr ( lev_config_name ( l ) , pad ) ;
printf ( " %4 " PRIu64 " | " , v ) ;
printc ( ' o ' , v * bar / io - > io_max ) ;
printf ( " \n " ) ;
}
2016-01-30 09:17:30 +01:00
printc ( ' ' , pad ) ;
2016-01-27 21:08:59 +01:00
printf ( " |_ " ) ;
printc ( ' _ ' , bar ) ;
printf ( " \n " ) ;
printc ( ' ' , 5 + pad + 1 + bar / 2 - strlen ( legend ) / 2 ) ;
2016-01-28 19:32:31 +01:00
printf ( " %s " , legend ) ;
2016-01-27 21:08:59 +01:00
printf ( " \n " ) ;
}
printf ( " \n " ) ;
2025-09-21 19:54:10 +02:00
pre = 4 ;
if ( state - > thermal_temperature_limit ! = 0 )
pre + = strlen ( THERMAL_PAD ) ;
if ( pad + pre + 30 < GRAPH_COLUMN )
bar = GRAPH_COLUMN - pad - pre ;
else
bar = 30 ; /* at least a bar of 30 */
2016-01-27 21:08:59 +01:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
2025-10-26 18:06:08 +01:00
2016-01-27 21:08:59 +01:00
v = disk - > progress_tick [ current ] - ref ( disk - > progress_tick , oldest ) ;
printr ( disk - > name , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 ) {
struct snapraid_thermal * thermal = state_thermal_find ( state , disk - > name ) ;
if ( thermal ) {
if ( thermal - > params . r_squared > = THERMAL_R_SQUARED_LIMIT )
printf ( " %2u (%2u) " , thermal - > latest_temperature , ( int ) thermal - > params . t_steady ) ;
else
printf ( " %2u " , thermal - > latest_temperature ) ;
} else {
printf ( THERMAL_PAD ) ;
}
}
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-27 21:08:59 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
2017-11-19 19:33:47 +01:00
/* clear the file in progress */
disk - > progress_file = 0 ;
2016-01-27 21:08:59 +01:00
}
for ( l = 0 ; l < state - > level ; + + l ) {
v = state - > parity [ l ] . progress_tick [ current ] - ref ( state - > parity [ l ] . progress_tick , oldest ) ;
printr ( lev_config_name ( l ) , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 ) {
struct snapraid_thermal * thermal = state_thermal_find ( state , lev_config_name ( l ) ) ;
if ( thermal ) {
if ( thermal - > params . r_squared > = THERMAL_R_SQUARED_LIMIT )
printf ( " %2u (%2u) " , thermal - > latest_temperature , ( int ) thermal - > params . t_steady ) ;
else
printf ( " %2u " , thermal - > latest_temperature ) ;
} else {
printf ( THERMAL_PAD ) ;
}
}
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-27 21:08:59 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
}
v = state - > progress_tick_raid [ current ] - ref ( state - > progress_tick_raid , oldest ) ;
printr ( " raid " , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-27 21:08:59 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
v = state - > progress_tick_hash [ current ] - ref ( state - > progress_tick_hash , oldest ) ;
printr ( " hash " , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-27 21:08:59 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
2016-01-31 16:38:29 +01:00
v = state - > progress_tick_sched [ current ] - ref ( state - > progress_tick_sched , oldest ) ;
printr ( " sched " , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-31 16:38:29 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
2016-01-27 21:08:59 +01:00
v = state - > progress_tick_misc [ current ] - ref ( state - > progress_tick_misc , oldest ) ;
printr ( " misc " , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2024-01-03 20:02:42 +01:00
printf ( " %3u%% | " , muldiv ( v , 100 , tick_total ) ) ;
2016-01-27 21:08:59 +01:00
printc ( ' * ' , v * bar / tick_total ) ;
printf ( " \n " ) ;
2016-01-30 09:17:30 +01:00
printc ( ' ' , pad ) ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2016-01-27 21:08:59 +01:00
printf ( " |_ " ) ;
printc ( ' _ ' , bar ) ;
printf ( " \n " ) ;
if ( oldest = = PROGRESS_MAX ) {
2016-02-05 20:20:15 +01:00
const char * legend = " wait time (total, less is better) " ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2016-01-27 21:08:59 +01:00
printc ( ' ' , 5 + pad + 1 + bar / 2 - strlen ( legend ) / 2 ) ;
2016-01-28 19:32:31 +01:00
printf ( " %s " , legend ) ;
2016-01-27 21:08:59 +01:00
printf ( " \n " ) ;
} else {
2016-02-05 20:20:15 +01:00
const char * legend_d = " wait time (last %d secs, less is better) " ;
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 )
printf ( THERMAL_PAD ) ;
2016-01-28 19:32:31 +01:00
printc ( ' ' , 5 + pad + 1 + bar / 2 - strlen ( legend_d ) / 2 ) ;
printf ( legend_d , PROGRESS_MAX ) ;
2016-01-27 21:08:59 +01:00
printf ( " \n " ) ;
}
2016-01-30 09:17:10 +01:00
printf ( " \n " ) ;
2016-01-27 21:08:59 +01:00
}
2016-01-27 20:37:31 +01:00
int state_progress ( struct snapraid_state * state , struct snapraid_io * io , block_off_t blockpos , block_off_t countpos , block_off_t countmax , data_off_t countsize )
2011-03-04 20:59:21 +01:00
{
2014-10-21 22:10:54 +02:00
time_t now ;
2026-09-07 00:04:49 +02:00
uint64_t thermal_now ;
2014-10-21 22:10:54 +02:00
int pred ;
2014-09-09 22:15:50 +02:00
2014-10-21 22:10:54 +02:00
now = time ( 0 ) ;
2026-09-07 00:04:49 +02:00
thermal_now = os_tick_sec ( ) ;
2014-09-09 22:15:50 +02:00
2025-09-21 19:54:10 +02:00
/* thermal measure */
2026-09-07 00:04:49 +02:00
if ( thermal_now > state - > thermal_latest + THERMAL_PERIOD_SECONDS | | state - > opt . fake_device ) {
state - > thermal_latest = thermal_now ;
2025-09-21 19:54:10 +02:00
state_thermal ( state , now ) ;
}
2014-10-21 22:10:54 +02:00
/* previous position */
pred = state - > progress_ptr + PROGRESS_MAX - 1 ;
if ( pred > = PROGRESS_MAX )
pred - = PROGRESS_MAX ;
/* if the previous measure is different */
2014-11-04 23:51:45 +01:00
if ( state - > progress_tick = = 0
| | state - > progress_time [ pred ] ! = now
) {
2015-01-06 22:28:38 +01:00
time_t elapsed ;
2014-10-21 22:10:54 +02:00
unsigned out_perc = 0 ;
2020-04-24 07:55:55 +02:00
unsigned out_size_speed = 0 ;
unsigned out_block_speed = 0 ;
2014-10-21 22:10:54 +02:00
unsigned out_cpu = 0 ;
unsigned out_eta = 0 ;
2025-09-21 19:54:10 +02:00
int out_temperature = 0 ;
int out_steady = 0 ;
2016-02-02 08:19:09 +01:00
int out_computed = 0 ;
2014-10-21 22:10:54 +02:00
2016-01-26 23:57:30 +01:00
/* store the new measure */
state - > progress_time [ state - > progress_ptr ] = now ;
state - > progress_pos [ state - > progress_ptr ] = countpos ;
state - > progress_size [ state - > progress_ptr ] = countsize ;
2016-01-27 21:08:59 +01:00
state_progress_latest ( state ) ;
2016-01-26 23:57:30 +01:00
2015-01-06 22:28:38 +01:00
elapsed = now - state - > progress_whole_start - state - > progress_wasted ;
2014-10-21 22:10:54 +02:00
/* completion percentage */
if ( countmax )
2024-01-03 20:02:42 +01:00
out_perc = muldiv ( countpos , 100 , countmax ) ;
2014-10-21 22:10:54 +02:00
/* if we have at least 5 measures */
2014-11-04 23:51:45 +01:00
if ( state - > progress_tick > = 5
2014-11-12 20:47:07 +01:00
/* or if we are running in test mode, with at least one measure */
2014-11-04 23:51:45 +01:00
| | ( state - > opt . force_progress & & state - > progress_tick > = 1 )
) {
2014-10-21 22:10:54 +02:00
int oldest ;
int past ;
time_t delta_time ;
block_off_t delta_pos ;
data_off_t delta_size ;
2016-01-26 23:57:30 +01:00
uint64_t tick_cpu ;
uint64_t tick_total ;
2014-10-21 22:10:54 +02:00
uint64_t delta_tick_cpu ;
2016-01-26 23:25:01 +01:00
uint64_t delta_tick_total ;
uint64_t oldest_tick_cpu ;
uint64_t oldest_tick_total ;
2025-09-21 19:54:10 +02:00
tommy_node * i ;
2014-10-21 22:10:54 +02:00
/* number of past measures */
past = state - > progress_tick ;
2026-04-27 15:43:06 +02:00
/*
* Drop the oldest ones , to promptly
* skip the startup phase
*/
2014-10-21 22:10:54 +02:00
past - = past / 5 ;
/* check how much we can go in the past */
2016-01-26 23:57:30 +01:00
if ( past > = PROGRESS_MAX - 1 ) {
2026-04-27 15:43:06 +02:00
/*
* The vector is filled , so we are already in position
* to get the possible oldest one
*/
2016-01-26 23:57:30 +01:00
oldest = state - > progress_ptr + 1 ;
2014-10-21 22:10:54 +02:00
} else {
/* go backward the number of positions selected */
oldest = state - > progress_ptr + PROGRESS_MAX - past ;
}
2016-01-26 23:57:30 +01:00
if ( oldest > = PROGRESS_MAX )
oldest - = PROGRESS_MAX ;
tick_cpu = state - > progress_tick_misc [ state - > progress_ptr ]
2016-01-31 16:38:29 +01:00
+ state - > progress_tick_sched [ state - > progress_ptr ]
2016-01-26 23:57:30 +01:00
+ state - > progress_tick_raid [ state - > progress_ptr ]
+ state - > progress_tick_hash [ state - > progress_ptr ] ;
tick_total = tick_cpu + state - > progress_tick_io [ state - > progress_ptr ] ;
2011-03-04 20:59:21 +01:00
2016-01-26 23:25:01 +01:00
oldest_tick_cpu = state - > progress_tick_misc [ oldest ]
2016-01-31 16:38:29 +01:00
+ state - > progress_tick_sched [ oldest ]
2016-01-26 23:25:01 +01:00
+ state - > progress_tick_raid [ oldest ]
+ state - > progress_tick_hash [ oldest ] ;
oldest_tick_total = oldest_tick_cpu + state - > progress_tick_io [ oldest ] ;
2014-10-21 22:10:54 +02:00
delta_time = now - state - > progress_time [ oldest ] ;
delta_pos = countpos - state - > progress_pos [ oldest ] ;
delta_size = countsize - state - > progress_size [ oldest ] ;
2016-01-26 23:25:01 +01:00
delta_tick_cpu = tick_cpu - oldest_tick_cpu ;
delta_tick_total = tick_total - oldest_tick_total ;
2014-09-09 22:33:09 +02:00
2016-01-16 07:27:28 +01:00
/* estimate the speed in MB/s */
2014-10-21 22:10:54 +02:00
if ( delta_time ! = 0 )
2020-04-24 07:55:55 +02:00
out_size_speed = ( unsigned ) ( delta_size / MEGA / delta_time ) ;
/* estimate the speed in block/s */
2021-05-24 19:47:05 +02:00
if ( delta_time ! = 0 )
2020-04-24 07:55:55 +02:00
out_block_speed = ( unsigned ) ( delta_pos / delta_time ) ;
2011-03-04 20:59:21 +01:00
2014-10-21 22:10:54 +02:00
/* estimate the cpu usage percentage */
if ( delta_tick_total ! = 0 )
2024-01-03 20:02:42 +01:00
out_cpu = muldiv ( delta_tick_cpu , 100 , delta_tick_total ) ;
2011-03-04 20:59:21 +01:00
2014-10-21 22:10:54 +02:00
/* estimate the remaining time in minutes */
if ( delta_pos ! = 0 )
2026-02-08 14:40:06 +01:00
out_eta = muldiv ( countmax - countpos , delta_time , delta_pos ) ;
2016-01-27 00:11:27 +01:00
2016-01-27 21:08:59 +01:00
if ( state - > opt . force_stats ) {
2016-01-27 20:39:00 +01:00
os_clear ( ) ;
2016-01-27 21:08:59 +01:00
state_progress_graph ( state , io , state - > progress_ptr , oldest ) ;
2016-01-27 00:11:27 +01:00
}
2016-02-02 08:19:09 +01:00
2025-09-21 19:54:10 +02:00
if ( state - > thermal_temperature_limit ! = 0 ) {
/* get the max temperature */
out_temperature = 0 ;
for ( i = tommy_list_head ( & state - > thermallist ) ; i ! = 0 ; i = i - > next ) {
struct snapraid_thermal * thermal = i - > data ;
if ( out_temperature < thermal - > latest_temperature ) {
out_temperature = thermal - > latest_temperature ;
if ( thermal - > params . r_squared > = THERMAL_R_SQUARED_LIMIT )
out_steady = thermal - > params . t_steady ;
else
out_steady = 0 ;
}
}
} else {
out_temperature = 0 ;
out_steady = 0 ;
}
2016-02-02 08:19:09 +01:00
/* we have the output value */
out_computed = 1 ;
2014-10-21 22:10:54 +02:00
}
2014-09-09 22:15:50 +02:00
2014-10-21 22:10:54 +02:00
if ( state - > opt . gui ) {
2026-02-08 14:40:06 +01:00
char str_eta [ 32 ] ;
char str_size_speed [ 32 ] ;
char str_cpu [ 32 ] ;
char str_temp [ 32 ] ;
char str_steady [ 32 ] ;
if ( out_computed ) {
snprintf ( str_eta , sizeof ( str_eta ) , " %u " , out_eta ) ;
snprintf ( str_size_speed , sizeof ( str_size_speed ) , " %u " , out_size_speed ) ;
snprintf ( str_cpu , sizeof ( str_cpu ) , " %u " , out_cpu ) ;
} else {
str_eta [ 0 ] = 0 ;
str_size_speed [ 0 ] = 0 ;
str_cpu [ 0 ] = 0 ;
}
if ( out_temperature ) {
2026-05-20 00:24:02 +02:00
snprintf ( str_temp , sizeof ( str_temp ) , " %u " , out_temperature ) ;
2026-02-08 14:40:06 +01:00
} else {
str_temp [ 0 ] = 0 ;
}
if ( out_steady ) {
snprintf ( str_steady , sizeof ( str_steady ) , " %u " , out_steady ) ;
} else {
str_steady [ 0 ] = 0 ;
}
2026-06-03 23:06:27 +02:00
log_tag ( " run:pos:% " PRIu64 " :% " PRIu64 " :% " PRIu64 " :%u:%s:%s:%s:% " PRIu64 " :%s:%s \n " , blockpos , countpos , countsize , out_perc , str_eta , str_size_speed , str_cpu , ( uint64_t ) elapsed , str_temp , str_steady ) ;
2015-04-20 22:07:42 +02:00
log_flush ( ) ;
2015-02-22 14:31:03 +01:00
} else {
2016-01-16 07:27:28 +01:00
msg_bar ( " %u%%, %u MB " , out_perc , ( unsigned ) ( countsize / MEGA ) ) ;
2016-02-02 08:19:09 +01:00
if ( out_computed ) {
2020-04-24 07:55:55 +02:00
msg_bar ( " , %u MB/s " , out_size_speed ) ;
2020-08-30 00:11:59 +02:00
msg_bar ( " , %u stripe/s " , out_block_speed ) ;
2016-02-02 08:19:09 +01:00
msg_bar ( " , CPU %u%% " , out_cpu ) ;
2025-09-21 19:54:10 +02:00
if ( out_temperature ! = 0 ) {
if ( out_steady ! = 0 )
msg_bar ( " , Tmax %u (%u) " , out_temperature , out_steady ) ;
else
msg_bar ( " , Tmax %u " , out_temperature ) ;
}
2026-02-08 14:40:06 +01:00
unsigned out_eta_minutes = out_eta / 60 ; /* minutes */
msg_bar ( " , %u:%02u ETA " , out_eta_minutes / 60 , out_eta_minutes % 60 ) ;
2016-02-02 08:19:09 +01:00
}
2025-10-12 23:55:32 +02:00
msg_bar ( " \r " ) ;
2015-03-23 22:57:36 +01:00
msg_flush ( ) ;
2014-10-21 22:10:54 +02:00
}
2014-09-09 22:15:50 +02:00
2016-01-26 23:57:30 +01:00
/* next position to fill */
2014-10-21 22:10:54 +02:00
+ + state - > progress_ptr ;
if ( state - > progress_ptr > = PROGRESS_MAX )
state - > progress_ptr - = PROGRESS_MAX ;
2014-09-09 22:15:50 +02:00
2014-10-21 22:10:54 +02:00
/* one more measure */
+ + state - > progress_tick ;
2011-03-04 20:59:21 +01:00
}
/* stop if requested */
2026-06-24 00:15:15 +02:00
if ( os_signal_interrupt ( ) ) {
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_START */
2013-06-29 18:11:36 +02:00
if ( ! state - > opt . gui ) {
2026-06-03 11:30:20 +02:00
msg_bar ( " \n " ) ;
msg_flush ( ) ;
2011-06-20 20:35:04 +02:00
}
2026-06-03 23:06:27 +02:00
log_fatal ( EUSER , " Stopping for interruption at block % " PRIu64 " \n " , blockpos ) ;
log_tag ( " sigint:% " PRIu64 " : SIGINT received \n " , blockpos ) ;
2015-04-20 22:07:42 +02:00
log_flush ( ) ;
2011-03-04 20:59:21 +01:00
return 1 ;
2014-03-20 22:10:19 +01:00
/* LCOV_EXCL_STOP */
2011-03-04 20:59:21 +01:00
}
return 0 ;
}
2016-01-20 21:30:00 +01:00
void state_usage_waste ( struct snapraid_state * state )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-20 21:30:00 +01:00
state - > tick_last = now ;
}
2016-01-26 23:25:01 +01:00
void state_usage_misc ( struct snapraid_state * state )
2016-01-20 21:30:00 +01:00
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-20 21:30:00 +01:00
uint64_t delta = now - state - > tick_last ;
/* increment the time spent in computations */
2016-01-26 23:25:01 +01:00
state - > tick_misc + = delta ;
state - > tick_last = now ;
}
2016-01-31 16:38:29 +01:00
void state_usage_sched ( struct snapraid_state * state )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-31 16:38:29 +01:00
uint64_t delta = now - state - > tick_last ;
/* increment the time spent in computations */
state - > tick_sched + = delta ;
state - > tick_last = now ;
}
2016-01-26 23:25:01 +01:00
void state_usage_raid ( struct snapraid_state * state )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-26 23:25:01 +01:00
uint64_t delta = now - state - > tick_last ;
/* increment the time spent in computations */
state - > tick_raid + = delta ;
state - > tick_last = now ;
}
void state_usage_hash ( struct snapraid_state * state )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-26 23:25:01 +01:00
uint64_t delta = now - state - > tick_last ;
/* increment the time spent in computations */
state - > tick_hash + = delta ;
2016-01-20 21:30:00 +01:00
state - > tick_last = now ;
}
2017-11-19 19:33:47 +01:00
void state_usage_file ( struct snapraid_state * state , struct snapraid_disk * disk , struct snapraid_file * file )
{
( void ) state ;
disk - > progress_file = file ;
}
2016-01-20 21:30:00 +01:00
void state_usage_disk ( struct snapraid_state * state , struct snapraid_handle * handle_map , unsigned * waiting_map , unsigned waiting_mac )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-20 21:30:00 +01:00
uint64_t delta = now - state - > tick_last ;
unsigned i ;
/* increment the time spent in the data disks */
for ( i = 0 ; i < waiting_mac ; + + i ) {
struct snapraid_disk * disk = handle_map [ waiting_map [ i ] ] . disk ;
if ( ! disk )
continue ;
disk - > tick + = delta ;
}
state - > tick_io + = delta ;
state - > tick_last = now ;
}
void state_usage_parity ( struct snapraid_state * state , unsigned * waiting_map , unsigned waiting_mac )
{
2026-03-02 01:13:19 +01:00
uint64_t now = os_tick ( ) ;
2016-01-20 21:30:00 +01:00
uint64_t delta = now - state - > tick_last ;
unsigned i ;
/* increment the time spent in the parity disk */
for ( i = 0 ; i < waiting_mac ; + + i )
state - > parity [ waiting_map [ i ] ] . tick + = delta ;
state - > tick_io + = delta ;
state - > tick_last = now ;
}
2014-06-25 22:32:36 +02:00
void state_usage_print ( struct snapraid_state * state )
{
2016-01-27 21:08:59 +01:00
/* set the latest data */
state_progress_latest ( state ) ;
2014-06-25 22:32:36 +02:00
2016-01-27 21:08:59 +01:00
if ( msg_level < MSG_PROGRESS )
2014-06-25 22:32:36 +02:00
return ;
2016-01-27 21:08:59 +01:00
/* print a graph for it */
state_progress_graph ( state , 0 , state - > progress_ptr , PROGRESS_MAX ) ;
2014-06-25 22:32:36 +02:00
}
2015-10-05 21:40:44 +02:00
void state_fscheck ( struct snapraid_state * state , const char * ope )
{
tommy_node * i ;
2016-01-02 16:52:20 +01:00
/* check the file-system on all disks */
2015-10-05 21:40:44 +02:00
for ( i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
if ( fs_check ( disk ) ! = 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_fatal ( EINTERNAL , " Internal inconsistency: File-system check for disk '%s' %s \n " , disk - > name , ope ) ;
2015-12-02 23:21:33 +01:00
os_abort ( ) ;
2015-10-05 21:40:44 +02:00
/* LCOV_EXCL_STOP */
}
}
}
2013-09-21 15:53:46 +02:00
void generate_configuration ( const char * path )
{
struct snapraid_state state ;
struct snapraid_content * content ;
2016-04-26 21:48:39 +02:00
unsigned l , s ;
2013-09-21 15:53:46 +02:00
tommy_node * j ;
state_init ( & state ) ;
/* mark that we are without a configuration file */
state . no_conf = 1 ;
/* create the dummy content entry */
content = content_alloc ( path , - 1 ) ;
/* adds the content entry */
tommy_list_insert_tail ( & state . contentlist , & content - > node , content ) ;
/* read the content file */
state_read ( & state ) ;
/* output a dummy configuration file */
2015-02-22 14:31:03 +01:00
printf ( " # Configuration file generated from %s \n " , path ) ;
printf ( " \n " ) ;
printf ( " # Use this blocksize \n " ) ;
2016-01-16 07:27:28 +01:00
printf ( " blocksize %u \n " , state . block_size / KIBI ) ;
2015-02-22 14:31:03 +01:00
printf ( " \n " ) ;
2016-04-07 22:37:11 +02:00
printf ( " # Use this hashsize \n " ) ;
2026-05-03 22:29:11 +02:00
printf ( " hashsize %zu \n " , BLOCK_HASH_SIZE ) ;
2016-04-07 22:37:11 +02:00
printf ( " \n " ) ;
2016-04-26 21:48:39 +02:00
for ( l = 0 ; l < state . level ; + + l ) {
printf ( " # Set the correct path for the %s files \n " , lev_name ( l ) ) ;
printf ( " # You had %u of them: \n " , state . parity [ l ] . split_mac ) ;
for ( s = 0 ; s < state . parity [ l ] . split_mac ; + + s ) {
printf ( " # %u: \n " , s ) ;
printf ( " # PATH: " ) ;
if ( state . parity [ l ] . split_map [ s ] . path [ 0 ] )
printf ( " %s " , state . parity [ l ] . split_map [ s ] . path ) ;
else
printf ( " ? " ) ;
printf ( " \n " ) ;
printf ( " # SIZE: " ) ;
if ( state . parity [ l ] . split_map [ s ] . size ! = PARITY_SIZE_INVALID )
printf ( " % " PRIu64 , state . parity [ l ] . split_map [ s ] . size ) ;
else
printf ( " ? " ) ;
printf ( " \n " ) ;
printf ( " # UUID: " ) ;
if ( state . parity [ l ] . split_map [ s ] . uuid [ 0 ] )
printf ( " %s " , state . parity [ l ] . split_map [ s ] . uuid ) ;
else
printf ( " ? " ) ;
printf ( " \n " ) ;
printf ( " # \n " ) ;
}
printf ( " %s ENTER_HERE_THE_PARITY_FILES_COMMA_SEPARATED \n " , lev_config_name ( l ) ) ;
2015-02-22 14:31:03 +01:00
printf ( " \n " ) ;
2013-09-21 16:04:22 +02:00
}
2015-02-22 14:31:03 +01:00
printf ( " # Add any other content file \n " ) ;
printf ( " content %s \n " , path ) ;
printf ( " \n " ) ;
2014-09-27 00:32:39 +02:00
for ( j = state . maplist ; j ; j = j - > next ) {
2013-09-21 15:53:46 +02:00
struct snapraid_map * map = j - > data ;
struct snapraid_disk * disk ;
2015-02-22 14:31:03 +01:00
printf ( " # Set the correct dir for disk '%s' \n " , map - > name ) ;
2026-06-28 23:35:44 +02:00
if ( map - > content_uuid [ 0 ] )
printf ( " # Disk '%s' is the one with id '%s' \n " , map - > name , map - > content_uuid ) ;
2015-12-28 13:55:22 +01:00
disk = find_disk_by_name ( & state , map - > name ) ;
2013-09-21 15:53:46 +02:00
if ( disk & & disk - > filelist ) {
struct snapraid_file * file = disk - > filelist - > data ;
if ( file ) {
2026-05-29 11:01:44 +02:00
printf ( " # and containing: %s \n " , fmt_poll ( disk , file - > sub ) ) ;
2013-09-21 15:53:46 +02:00
}
}
2016-01-10 21:19:20 +01:00
printf ( " data %s ENTER_HERE_THE_DIR \n " , map - > name ) ;
2015-02-22 14:31:03 +01:00
printf ( " \n " ) ;
2013-09-21 15:53:46 +02:00
}
state_done ( & state ) ;
}
2025-09-25 12:46:15 +02:00
/**
2026-08-17 19:33:26 +02:00
* Establish a durable I / O barrier : drain asynchronous writes , collect their
* errors , sync parity only if all writes succeeded , then quiesce read - ahead .
* A failed writer invalidates the whole barrier because parity must not be
* treated as coherent , or recorded in content state , after a failed write .
2025-09-25 12:46:15 +02:00
*/
2026-08-17 19:33:26 +02:00
int state_barrier ( struct snapraid_state * state , struct snapraid_io * io , struct snapraid_parity_handle * parity_handle , block_off_t blockcur )
2025-09-25 12:46:15 +02:00
{
unsigned l ;
2026-08-17 18:28:26 +02:00
if ( io ) {
int writer_error [ IO_WRITER_ERROR_MAX ] ;
unsigned j ;
io_flush ( io , writer_error ) ;
for ( j = 0 ; j < IO_WRITER_ERROR_MAX ; + + j ) {
if ( writer_error [ j ] ) {
errno = EIO ;
return - 1 ;
}
}
}
2025-09-25 12:46:15 +02:00
/* flush all parity handles to ensure data is written to disk */
for ( l = 0 ; l < state - > level ; + + l ) {
int ret = parity_sync ( & parity_handle [ l ] ) ;
if ( ret = = - 1 ) {
/* LCOV_EXCL_START */
2026-06-03 23:06:27 +02:00
log_tag ( " parity_error_io:% " PRIu64 " :%s: Sync error. %s. \n " , blockcur , lev_config_name ( l ) , strerror ( errno ) ) ;
2026-02-01 20:21:28 +01:00
log_fatal ( errno , " DANGER! Unexpected input/output error in disk %s. It isn't possible to continue. \n " , lev_config_name ( l ) ) ;
2025-09-25 12:46:15 +02:00
return - 1 ;
/* LCOV_EXCL_STOP */
}
}
2025-10-26 18:06:08 +01:00
2026-08-17 19:33:26 +02:00
/* complete all read-ahead without consuming the results */
if ( io )
io_quiesce ( io ) ;
2025-09-25 12:46:15 +02:00
return 0 ;
}
2025-10-26 18:06:08 +01:00
2026-01-10 16:30:35 +01:00
void state_load_ignore_file ( tommy_list * filter_list , const char * path , const char * sub )
{
STREAM * f ;
int line ;
f = sopen_read ( path , 0 ) ;
if ( ! f ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error opening the ignore file '%s'. %s. \n " , path , strerror ( errno ) ) ;
2026-01-26 14:41:43 +01:00
return ;
2026-01-10 16:30:35 +01:00
/* LCOV_EXCL_STOP */
}
line = 1 ;
while ( 1 ) {
char buffer [ PATH_MAX ] ;
2026-06-04 00:34:51 +02:00
ssize_t ret ;
2026-01-10 16:30:35 +01:00
int c ;
/* skip initial spaces */
sgetspace ( f ) ;
/* get the whole line */
ret = sgetline ( f , buffer , sizeof ( buffer ) ) ;
if ( ret < 0 ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_error ( EUSER , " Too long line in '%s' at line %u \n " , path , line ) ;
2026-01-26 14:41:43 +01:00
break ;
2026-01-10 16:30:35 +01:00
/* LCOV_EXCL_STOP */
}
if ( buffer [ 0 ] = = 0 ) {
/* allow empty lines */
} else if ( buffer [ 0 ] = = ' # ' ) {
/* ignore comment lines */
} else {
struct snapraid_filter * filter ;
filter = filter_alloc_file ( - 1 , sub , buffer ) ;
if ( ! filter ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_error ( EUSER , " Invalid ignore specification '%s' in '%s' at line %u \n " , buffer , path , line ) ;
2026-01-26 14:41:43 +01:00
break ;
2026-01-10 16:30:35 +01:00
/* LCOV_EXCL_STOP */
}
tommy_list_insert_tail ( filter_list , & filter - > node , filter ) ;
}
/* next line */
c = sgeteol ( f ) ;
if ( c = = EOF ) {
break ;
}
if ( c ! = ' \n ' ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_error ( EUSER , " Extra data in '%s' at line %u \n " , path , line ) ;
2026-01-26 14:41:43 +01:00
break ;
2026-01-10 16:30:35 +01:00
/* LCOV_EXCL_STOP */
}
+ + line ;
}
if ( serror ( f ) ) {
/* LCOV_EXCL_START */
2026-02-01 20:21:28 +01:00
log_error ( errno , " Error reading the ignore file '%s' at line %u \n " , path , line ) ;
2026-01-10 16:30:35 +01:00
/* LCOV_EXCL_STOP */
}
sclose ( f ) ;
}
2026-08-25 23:09:39 +02:00
static int state_snapshot_dir ( struct fssnapshot_struct * fss , const char * name , struct snapraid_disk * disk )
2026-04-28 00:35:15 +02:00
{
struct stat st ;
2026-05-04 11:36:34 +02:00
/* fail if it doesn't exists at high level */
if ( fssnapshot_stat ( fss , name , & st ) ! = 0 )
2026-04-28 00:35:15 +02:00
return - 1 ;
if ( disk ) {
2026-05-04 11:36:34 +02:00
char vol [ PATH_MAX ] ;
2026-08-26 00:37:52 +02:00
if ( fssnapshot_path ( fss , name , vol , sizeof ( vol ) ) ! = 0 )
return - 1 ;
2026-05-04 11:36:34 +02:00
pathcpy ( disk - > dir , sizeof ( disk - > dir ) , vol ) ;
2026-04-28 00:35:15 +02:00
}
return 0 ;
}
2026-08-12 14:53:58 +02:00
int state_snapshot_scan ( struct snapraid_state * state )
2026-04-21 15:19:25 +02:00
{
if ( ! state - > snapshot )
return 0 ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
2026-08-12 15:03:15 +02:00
int res = fssnapshot_mount ( disk - > mount_point , & disk - > fss ) ;
if ( res > 0 )
2026-04-21 15:19:25 +02:00
continue ;
2026-08-12 15:03:15 +02:00
if ( res < 0 ) {
log_fatal ( EINTERNAL , " Failed to setup snapshot for disk '%s'. \n " , disk - > name ) ;
return - 1 ;
}
2026-04-21 15:19:25 +02:00
2026-09-07 22:34:53 +02:00
msg_progress ( " Creating disk %s scan snapshot... \n " , disk - > name ) ;
2026-08-12 14:53:58 +02:00
/* delete a potential previous scan snapshot */
2026-08-25 23:09:39 +02:00
if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_SCAN , 0 ) = = 0 ) {
2026-08-12 14:53:58 +02:00
if ( fssnapshot_delete ( & disk - > fss , SNAPSHOT_SCAN ) ! = 0 ) {
log_fatal ( errno , " Failed to delete scan snapshot in '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
return - 1 ;
}
}
/* create a new snapshot for scanning */
if ( fssnapshot_create ( & disk - > fss , SNAPSHOT_SCAN ) ! = 0 ) {
log_fatal ( errno , " Failed to create scan snapshot '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
return - 1 ;
}
/* setup the snapshot in use */
2026-08-25 23:09:39 +02:00
if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_SCAN , disk ) ! = 0 ) {
2026-08-12 14:53:58 +02:00
log_error ( errno , " Error stating scan snapshot '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
return - 1 ;
}
}
return 0 ;
}
int state_snapshot_pending ( struct snapraid_state * state )
{
2026-08-13 22:15:42 +02:00
int error = 0 ;
2026-08-12 14:53:58 +02:00
if ( ! state - > snapshot )
return 0 ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
if ( disk - > fss . magic = = 0 )
continue ;
2026-09-07 22:34:53 +02:00
msg_progress ( " Creating disk %s pending snapshot... \n " , disk - > name ) ;
2026-09-08 22:19:55 +02:00
/* publish the scan snapshot without dropping the previous recovery source first */
if ( fssnapshot_replace ( & disk - > fss , SNAPSHOT_SCAN , SNAPSHOT_PENDING ) ! = 0 ) {
log_fatal ( errno , " Failed to replace pending snapshot in '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
2026-08-13 22:15:42 +02:00
error = - 1 ;
continue ;
2026-04-21 15:19:25 +02:00
}
/* setup the snapshot in use */
2026-08-25 23:09:39 +02:00
if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_PENDING , disk ) ! = 0 ) {
2026-05-04 11:36:34 +02:00
log_error ( errno , " Error stating pending snapshot '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
2026-08-13 22:15:42 +02:00
error = - 1 ;
continue ;
2026-04-28 00:35:15 +02:00
}
2026-04-21 15:19:25 +02:00
}
2026-08-13 22:15:42 +02:00
return error ;
2026-04-21 15:19:25 +02:00
}
int state_snapshot_commit ( struct snapraid_state * state )
{
2026-08-13 22:15:42 +02:00
int error = 0 ;
2026-04-21 15:19:25 +02:00
if ( ! state - > snapshot )
return 0 ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
2026-05-04 10:59:12 +02:00
if ( disk - > fss . magic = = 0 )
2026-04-21 15:19:25 +02:00
continue ;
2026-09-07 22:34:53 +02:00
msg_progress ( " Committing disk %s stable snapshot... \n " , disk - > name ) ;
2026-09-08 22:19:55 +02:00
/* publish pending without dropping the previous stable deallocation source first */
if ( fssnapshot_replace ( & disk - > fss , SNAPSHOT_PENDING , SNAPSHOT_STABLE ) ! = 0 ) {
log_fatal ( errno , " Failed to replace stable snapshot in '%s'. %s. \n " , disk - > fss . snapshot_dir , strerror ( errno ) ) ;
2026-08-13 22:15:42 +02:00
error = - 1 ;
continue ;
2026-04-21 15:19:25 +02:00
}
}
2026-08-13 22:15:42 +02:00
return error ;
2026-04-21 15:19:25 +02:00
}
void state_snapshot_read ( struct snapraid_state * state )
{
if ( ! state - > snapshot )
return ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
2026-08-12 15:22:23 +02:00
int res = fssnapshot_mount ( disk - > mount_point , & disk - > fss ) ;
if ( res > 0 )
2026-04-21 15:19:25 +02:00
continue ;
2026-08-12 15:22:23 +02:00
if ( res < 0 ) {
2026-09-08 22:19:55 +02:00
log_error ( EUSER , " WARNING! Disk %s snapshot setup or recovery failed, falling back to live filesystem. \n " , disk - > name ) ;
2026-08-25 22:39:56 +02:00
log_error ( EUSER , " Recovery capability may be reduced if files have changed since the last successful sync. \n " ) ;
2026-08-12 15:22:23 +02:00
continue ;
}
2026-04-21 15:19:25 +02:00
2026-08-25 23:09:39 +02:00
if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_PENDING , disk ) = = 0 ) {
2026-04-21 15:19:25 +02:00
msg_progress ( " Using disk %s pending snapshot... \n " , disk - > name ) ;
} else {
2026-08-25 23:09:39 +02:00
if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_STABLE , disk ) = = 0 ) {
2026-04-21 15:19:25 +02:00
msg_progress ( " Using disk %s stable snapshot... \n " , disk - > name ) ;
} else {
2026-04-28 00:35:15 +02:00
/* fallback to standard mount point */
2026-08-25 22:39:56 +02:00
log_error ( EUSER , " WARNING! Disk %s snapshot missing, falling back to live filesystem. \n " , disk - > name ) ;
log_error ( EUSER , " Recovery capability may be reduced if files have changed since the last successful sync. \n " ) ;
2026-04-21 15:19:25 +02:00
msg_progress ( " Using disk %s live filesystem... \n " , disk - > name ) ;
}
}
}
}
void state_snapshot_write ( struct snapraid_state * state , tommy_list * filterlist_disk )
{
if ( ! state - > snapshot )
return ;
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
2026-08-12 15:22:23 +02:00
int res = fssnapshot_mount ( disk - > mount_point , & disk - > fss ) ;
if ( res > 0 )
2026-04-21 15:19:25 +02:00
continue ;
2026-08-12 15:22:23 +02:00
if ( res < 0 ) {
2026-09-08 22:19:55 +02:00
log_error ( EUSER , " WARNING! Disk %s snapshot setup or recovery failed, falling back to live filesystem. \n " , disk - > name ) ;
2026-08-25 22:39:56 +02:00
log_error ( EUSER , " Recovery capability may be reduced if files have changed since the last successful sync. \n " ) ;
2026-08-12 15:22:23 +02:00
continue ;
}
2026-04-21 15:19:25 +02:00
2026-09-08 11:01:51 +02:00
int writable = tommy_list_empty ( filterlist_disk ) | | filter_path ( filterlist_disk , 0 , disk - > name , 0 ) = = 0 ;
2026-04-21 15:19:25 +02:00
2026-09-08 11:01:51 +02:00
if ( writable ) {
msg_progress ( " Using disk %s live filesystem... \n " , disk - > name ) ;
} else if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_PENDING , disk ) = = 0 ) {
2026-04-21 15:19:25 +02:00
msg_progress ( " Using disk %s pending snapshot... \n " , disk - > name ) ;
2026-09-08 11:01:51 +02:00
} else if ( state_snapshot_dir ( & disk - > fss , SNAPSHOT_STABLE , disk ) = = 0 ) {
msg_progress ( " Using disk %s stable snapshot... \n " , disk - > name ) ;
} else {
/* fallback to standard mount point */
log_error ( EUSER , " WARNING! Disk %s snapshot missing, falling back to live filesystem. \n " , disk - > name ) ;
log_error ( EUSER , " Recovery capability may be reduced if files have changed since the last successful sync. \n " ) ;
msg_progress ( " Using disk %s live filesystem... \n " , disk - > name ) ;
}
2026-04-27 14:39:38 +02:00
2026-09-08 11:01:51 +02:00
/*
* If sync was interrupted before updating parity , the content file
* already records BLOCK_STATE_DELETED and pending deallocations , while the
* deleted files remain physically present in the stable snapshot . Import them
* into the import index so OLD recovery hypotheses can resolve them without
* consuming RAID equations , even for disks that use the live filesystem for normal I / O .
*/
if ( ! tommy_list_empty ( & disk - > dealloclist ) & & state_snapshot_dir ( & disk - > fss , SNAPSHOT_STABLE , 0 ) = = 0 ) {
char vol [ PATH_MAX ] ;
2026-04-27 14:39:38 +02:00
2026-09-08 11:01:51 +02:00
msg_progress ( " Importing disk %s stable snapshot % " PRIu64 " deallocated files... \n " , disk - > name , ( uint64_t ) tommy_list_count ( & disk - > dealloclist ) ) ;
2026-05-04 11:36:34 +02:00
2026-09-08 11:01:51 +02:00
if ( fssnapshot_path ( & disk - > fss , SNAPSHOT_STABLE , vol , sizeof ( vol ) ) = = 0 )
state_dealloc ( state , vol , & disk - > dealloclist ) ;
2026-04-21 15:19:25 +02:00
}
}
}
2026-05-15 09:15:27 +02:00
void state_snapshot_cleanup ( struct snapraid_state * state )
{
for ( tommy_node * i = state - > disklist ; i ! = 0 ; i = i - > next ) {
struct snapraid_disk * disk = i - > data ;
/* check if it supports snapshot */
if ( disk - > fss . magic = = 0 )
continue ;
fssnapshot_unmount ( & disk - > fss ) ;
}
}
2026-08-18 23:04:56 +02:00