LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_ssarchive.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 126 215 58.6 %
Date: 2026-04-01 06:30:45 Functions: 2 3 66.7 %

          Line data    Source code
       1             : #include "fd_ssarchive.h"
       2             : 
       3             : #include "../../../util/log/fd_log.h"
       4             : 
       5             : #include <errno.h>
       6             : #include <dirent.h>
       7             : #include <stdlib.h>
       8             : #include <unistd.h>
       9             : 
      10             : struct fd_ssarchive_entry {
      11             :   ulong slot;
      12             :   ulong base_slot;
      13             :   int   is_zstd;
      14             :   char  path[ PATH_MAX ];
      15             :   uchar hash[ FD_HASH_FOOTPRINT ];
      16             : };
      17             : typedef struct fd_ssarchive_entry fd_ssarchive_entry_t;
      18             : 
      19             : #define SORT_NAME  sort_ssarchive_entries
      20          24 : #define SORT_KEY_T fd_ssarchive_entry_t
      21          12 : #define SORT_BEFORE(a,b) ( (a).slot>(b).slot )
      22             : #include "../../../util/tmpl/fd_sort.c"
      23             : 
      24             : #define FD_SSARCHIVE_MAX_ENTRIES (512UL)
      25             : 
      26             : int
      27             : fd_ssarchive_parse_filename( char const * _name,
      28             :                              ulong *      full_slot,
      29             :                              ulong *      incremental_slot,
      30             :                              uchar        hash[ static FD_HASH_FOOTPRINT ],
      31          33 :                              int *        is_zstd ) {
      32          33 :   char name[ PATH_MAX ];
      33          33 :   fd_cstr_ncpy( name, _name, sizeof(name) );
      34             : 
      35          33 :   char * ptr = name;
      36          33 :   int is_incremental;
      37          33 :   if( !strncmp( ptr, "incremental-snapshot-", 21UL ) ) {
      38          15 :     is_incremental = 1;
      39          15 :     ptr += 21UL;
      40          18 :   } else if( !strncmp( ptr, "snapshot-", 9UL ) ) {
      41          18 :     is_incremental = 0;
      42          18 :     ptr += 9UL;
      43          18 :   } else {
      44           0 :     return -1;
      45           0 :   }
      46             : 
      47          33 :   char * next = strchr( ptr, '-' );
      48          33 :   if( FD_UNLIKELY( !next ) ) return -1;
      49          33 :   *next = '\0';
      50          33 :   char * endptr;
      51          33 :   *full_slot = strtoul( ptr, &endptr, 10 );
      52          33 :   if( FD_UNLIKELY( *endptr!='\0' || endptr==ptr || *full_slot==ULONG_MAX ) ) return -1;
      53             : 
      54          33 :   if( is_incremental ) {
      55          15 :     ptr = next + 1;
      56          15 :     next = strchr( ptr, '-' );
      57          15 :     if( FD_UNLIKELY( !next ) ) return -1;
      58          15 :     *next = '\0';
      59          15 :     *incremental_slot = strtoul( ptr, &endptr, 10 );
      60          15 :     if( FD_UNLIKELY( *endptr!='\0' || endptr==ptr || *incremental_slot==ULONG_MAX ) ) return -1;
      61          18 :   } else {
      62          18 :     *incremental_slot = ULONG_MAX;
      63          18 :   }
      64             : 
      65          33 :   ptr = next + 1;
      66          33 :   next = strchr( ptr, '.' );
      67          33 :   if( FD_UNLIKELY( !next ) ) return -1;
      68          33 :   *next = '\0';
      69          33 :   ulong sz = (ulong)(next - ptr);
      70          33 :   if( FD_UNLIKELY( sz>FD_BASE58_ENCODED_32_LEN ) ) return -1;
      71          33 :   uchar * result = fd_base58_decode_32( ptr, hash );
      72          33 :   if( FD_UNLIKELY( result!=hash ) ) return -1;
      73             : 
      74          33 :   ptr = next + 1;
      75             : 
      76          33 :   if(       FD_LIKELY( 0==strcmp( ptr, "tar.zst" ) ) ) *is_zstd = 1;
      77           0 :   else if ( FD_LIKELY( 0==strcmp( ptr, "tar"     ) ) ) *is_zstd = 0;
      78           0 :   else return -1;
      79             : 
      80          33 :   return 0;
      81          33 : }
      82             : 
      83             : int
      84             : fd_ssarchive_latest_pair( char const * directory,
      85             :                           int          incremental_snapshot,
      86             :                           ulong *      full_slot,
      87             :                           ulong *      incremental_slot,
      88             :                           char         full_path[ static PATH_MAX ],
      89             :                           char         incremental_path[ static PATH_MAX ],
      90             :                           int *        full_is_zstd,
      91             :                           int *        incremental_is_zstd,
      92             :                           uchar        full_hash[ static FD_HASH_FOOTPRINT ],
      93           9 :                           uchar        incremental_hash[ static FD_HASH_FOOTPRINT ] ) {
      94           9 :   *full_slot = ULONG_MAX;
      95           9 :   *incremental_slot = ULONG_MAX;
      96             : 
      97           9 :   DIR * dir = opendir( directory );
      98           9 :   if( FD_UNLIKELY( !dir ) ) {
      99           0 :     if( FD_LIKELY( errno==ENOENT ) ) return -1;
     100           0 :     FD_LOG_ERR(( "opendir() failed `%s` (%i-%s)", directory, errno, fd_io_strerror( errno ) ));
     101           0 :   }
     102             : 
     103           9 :   fd_ssarchive_entry_t full_snapshots[ FD_SSARCHIVE_MAX_ENTRIES ];
     104           9 :   fd_ssarchive_entry_t incremental_snapshots[ FD_SSARCHIVE_MAX_ENTRIES ];
     105           9 :   ulong full_snapshots_cnt        = 0UL;
     106           9 :   ulong incremental_snapshots_cnt = 0UL;
     107             : 
     108           9 :   struct dirent * entry;
     109           9 :   errno = 0;
     110          60 :   while(( entry = readdir( dir ) )) {
     111          51 :     if( FD_LIKELY( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) ) ) continue;
     112             : 
     113          33 :     int is_zstd;
     114          33 :     ulong entry_full_slot, entry_incremental_slot;
     115          33 :     uchar decoded_hash[ FD_HASH_FOOTPRINT ];
     116          33 :     if( FD_UNLIKELY( -1==fd_ssarchive_parse_filename( entry->d_name, &entry_full_slot, &entry_incremental_slot, decoded_hash, &is_zstd ) ) ) {
     117           0 :       FD_LOG_INFO(( "unrecognized snapshot file `%s/%s` in snapshots directory", directory, entry->d_name ));
     118           0 :       continue;
     119           0 :     }
     120             : 
     121          33 :     if( FD_LIKELY( entry_incremental_slot==ULONG_MAX ) ) {
     122          18 :       if( FD_UNLIKELY( full_snapshots_cnt>=FD_SSARCHIVE_MAX_ENTRIES ) ) {
     123           0 :         continue;
     124           0 :       }
     125             : 
     126          18 :       full_snapshots[ full_snapshots_cnt ].slot      = entry_full_slot;
     127          18 :       full_snapshots[ full_snapshots_cnt ].base_slot = ULONG_MAX;
     128          18 :       full_snapshots[ full_snapshots_cnt ].is_zstd   = is_zstd;
     129          18 :       if( FD_UNLIKELY( !fd_cstr_printf_check( full_snapshots[ full_snapshots_cnt ].path, PATH_MAX, NULL, "%s/%s", directory, entry->d_name ) ) ) {
     130           0 :         FD_LOG_ERR(( "snapshot path too long `%s/%s`", directory, entry->d_name ));
     131           0 :       }
     132          18 :       fd_memcpy( full_snapshots[ full_snapshots_cnt ].hash, decoded_hash, FD_HASH_FOOTPRINT );
     133          18 :       full_snapshots_cnt++;
     134          18 :     } else {
     135          15 :       if( FD_UNLIKELY( incremental_snapshots_cnt>=FD_SSARCHIVE_MAX_ENTRIES ) ) {
     136           0 :         continue;
     137           0 :       }
     138             : 
     139          15 :       incremental_snapshots[ incremental_snapshots_cnt ].slot      = entry_incremental_slot;
     140          15 :       incremental_snapshots[ incremental_snapshots_cnt ].base_slot = entry_full_slot;
     141          15 :       incremental_snapshots[ incremental_snapshots_cnt ].is_zstd   = is_zstd;
     142          15 :       if( FD_UNLIKELY( !fd_cstr_printf_check( incremental_snapshots[ incremental_snapshots_cnt ].path, PATH_MAX, NULL, "%s/%s", directory, entry->d_name ) ) ) {
     143           0 :         FD_LOG_ERR(( "snapshot path too long `%s/%s`", directory, entry->d_name ));
     144           0 :       }
     145          15 :       fd_memcpy( incremental_snapshots[ incremental_snapshots_cnt ].hash, decoded_hash, FD_HASH_FOOTPRINT );
     146          15 :       incremental_snapshots_cnt++;
     147          15 :     }
     148          33 :   }
     149             : 
     150           9 :   if( FD_UNLIKELY( -1==closedir( dir ) ) ) FD_LOG_ERR(( "closedir() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     151           9 :   if( FD_UNLIKELY( errno && errno!=ENOENT ) ) FD_LOG_ERR(( "readdir() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     152             : 
     153           9 :   if( FD_LIKELY( incremental_snapshot ) ) {
     154           6 :     if( FD_UNLIKELY( incremental_snapshots_cnt==0UL && full_snapshots_cnt==0UL ) ) return -1;
     155           6 :     if( FD_UNLIKELY( full_snapshots_cnt==0UL ) )                                   return -1;
     156             : 
     157           6 :     sort_ssarchive_entries_inplace( incremental_snapshots, incremental_snapshots_cnt );
     158           6 :     sort_ssarchive_entries_inplace( full_snapshots, full_snapshots_cnt );
     159             : 
     160           6 :     if( FD_UNLIKELY( incremental_snapshots_cnt==0UL ) ) {
     161           0 :       FD_LOG_WARNING(("no incremental snapshots found in `%s`, falling back to latest full snapshot", directory ));
     162           0 :       *full_slot           = full_snapshots[ 0UL ].slot;
     163           0 :       *full_is_zstd        = full_snapshots[ 0UL ].is_zstd;
     164           0 :       *incremental_slot    = ULONG_MAX;
     165           0 :       *incremental_is_zstd = 0;
     166           0 :       FD_TEST( fd_cstr_printf_check( full_path, PATH_MAX, NULL, "%s", full_snapshots[ 0UL ].path ) );
     167           0 :       fd_memcpy( full_hash, full_snapshots[ 0UL ].hash, FD_HASH_FOOTPRINT );
     168           0 :       memset( incremental_hash, 0, FD_HASH_FOOTPRINT );
     169           0 :       return 0;
     170           0 :     }
     171             : 
     172           9 :     for( ulong i=0UL; i<incremental_snapshots_cnt; i++ ) {
     173           6 :       ulong base_slot = incremental_snapshots[ i ].base_slot;
     174          12 :       for( ulong j=0; j<full_snapshots_cnt; j++ ) {
     175          12 :         if( FD_LIKELY( full_snapshots[ j ].slot==base_slot ) ) {
     176           3 :           *full_slot           = base_slot;
     177           3 :           *incremental_slot    = incremental_snapshots[ i ].slot;
     178           3 :           *full_is_zstd        = full_snapshots[ j ].is_zstd;
     179           3 :           *incremental_is_zstd = incremental_snapshots[ i ].is_zstd;
     180           3 :           FD_TEST( fd_cstr_printf_check( full_path, PATH_MAX, NULL, "%s", full_snapshots[ j ].path ) );
     181           3 :           FD_TEST( fd_cstr_printf_check( incremental_path, PATH_MAX, NULL, "%s", incremental_snapshots[ i ].path ) );
     182           3 :           fd_memcpy( full_hash, full_snapshots[ j ].hash, FD_HASH_FOOTPRINT );
     183           3 :           fd_memcpy( incremental_hash, incremental_snapshots[ i ].hash, FD_HASH_FOOTPRINT );
     184           3 :           return 0;
     185           9 :         } else if( FD_LIKELY( full_snapshots[ j ].slot<base_slot ) ) {
     186             :             /* full snapshots are sorted in descending order, so if we reach a
     187             :                full snapshot with slot smaller than the incremental snapshot's
     188             :                base slot, we can stop searching.  */
     189           3 :             break;
     190           3 :         }
     191          12 :       }
     192           6 :     }
     193             : 
     194             :     /* if we reach here, it means all incrementals are dangling (they
     195             :        don't build off any full snapshot). fallback to a full
     196             :        snapshot in that case. */
     197           3 :     *full_slot           = full_snapshots[ 0UL ].slot;
     198           3 :     *full_is_zstd        = full_snapshots[ 0UL ].is_zstd;
     199           3 :     *incremental_slot    = ULONG_MAX;
     200           3 :     *incremental_is_zstd = 0;
     201           3 :     FD_TEST( fd_cstr_printf_check( full_path, PATH_MAX, NULL, "%s", full_snapshots[ 0UL ].path ) );
     202           3 :     incremental_path[ 0UL ] = '\0';
     203           3 :     fd_memcpy( full_hash, full_snapshots[ 0UL ].hash, FD_HASH_FOOTPRINT );
     204           3 :     memset( incremental_hash, 0, FD_HASH_FOOTPRINT );
     205           3 :     return 0;
     206             : 
     207           3 :   } else {
     208           3 :     if( FD_UNLIKELY( full_snapshots_cnt==0UL ) ) return -1;
     209             : 
     210           3 :     sort_ssarchive_entries_inplace( full_snapshots, full_snapshots_cnt );
     211             : 
     212           3 :     *full_slot           = full_snapshots[ 0UL ].slot;
     213           3 :     *full_is_zstd        = full_snapshots[ 0UL ].is_zstd;
     214           3 :     *incremental_slot    = ULONG_MAX;
     215           3 :     *incremental_is_zstd = 0;
     216           3 :     FD_TEST( fd_cstr_printf_check( full_path, PATH_MAX, NULL, "%s", full_snapshots[ 0UL ].path ) );
     217           3 :     incremental_path[ 0UL ] = '\0';
     218           3 :     fd_memcpy( full_hash, full_snapshots[ 0UL ].hash, FD_HASH_FOOTPRINT );
     219           3 :     memset( incremental_hash, 0, FD_HASH_FOOTPRINT );
     220           3 :     return 0;
     221           3 :   }
     222           9 : }
     223             : 
     224             : void
     225             : fd_ssarchive_remove_old_snapshots( char const * directory,
     226             :                                    uint         max_full_snapshots_to_keep,
     227           0 :                                    uint         max_incremental_snapshots_to_keep ) {
     228           0 :   ulong full_snapshots_cnt        = 0UL;
     229           0 :   ulong incremental_snapshots_cnt = 0UL;
     230           0 :   fd_ssarchive_entry_t full_snapshots[ FD_SSARCHIVE_MAX_ENTRIES ];
     231           0 :   fd_ssarchive_entry_t incremental_snapshots[ FD_SSARCHIVE_MAX_ENTRIES ];
     232             : 
     233           0 :   DIR * dir = opendir( directory );
     234           0 :   if( FD_UNLIKELY( !dir ) ) {
     235           0 :     if( FD_LIKELY( errno==ENOENT ) ) return;
     236           0 :     FD_LOG_ERR(( "opendir() failed `%s` (%i-%s)", directory, errno, fd_io_strerror( errno ) ));
     237           0 :   }
     238             : 
     239           0 :   struct dirent * entry;
     240             : 
     241           0 :   errno = 0;
     242           0 :   while(( entry = readdir( dir ) )) {
     243           0 :     if( FD_LIKELY( !strcmp( entry->d_name, "." ) || !strcmp( entry->d_name, ".." ) ) ) continue;
     244             : 
     245           0 :     int is_zstd;
     246           0 :     ulong entry_full_slot, entry_incremental_slot;
     247           0 :     uchar decoded_hash[ FD_HASH_FOOTPRINT ];
     248           0 :     if( FD_UNLIKELY( -1==fd_ssarchive_parse_filename( entry->d_name, &entry_full_slot, &entry_incremental_slot, decoded_hash, &is_zstd ) ) ) {
     249           0 :       FD_LOG_INFO(( "unrecognized snapshot file `%s/%s` in snapshots directory", directory, entry->d_name ));
     250           0 :       continue;
     251           0 :     }
     252             : 
     253           0 :     if( FD_LIKELY( entry_incremental_slot==ULONG_MAX ) ) {
     254           0 :       FD_TEST( entry_full_slot!=ULONG_MAX );
     255             : 
     256           0 :       if( FD_UNLIKELY( full_snapshots_cnt>=FD_SSARCHIVE_MAX_ENTRIES ) ) {
     257           0 :         continue;
     258           0 :       }
     259             : 
     260           0 :       full_snapshots[ full_snapshots_cnt ].slot      = entry_full_slot;
     261           0 :       full_snapshots[ full_snapshots_cnt ].base_slot = ULONG_MAX;
     262           0 :       full_snapshots[ full_snapshots_cnt ].is_zstd   = is_zstd;
     263           0 :       FD_TEST( fd_cstr_printf_check( full_snapshots[ full_snapshots_cnt ].path, PATH_MAX, NULL, "%s/%s", directory, entry->d_name ) );
     264           0 :       full_snapshots_cnt++;
     265           0 :     } else {
     266             : 
     267           0 :       if( FD_UNLIKELY( incremental_snapshots_cnt>=FD_SSARCHIVE_MAX_ENTRIES ) ) {
     268           0 :         continue;
     269           0 :       }
     270             : 
     271           0 :       incremental_snapshots[ incremental_snapshots_cnt ].slot      = entry_incremental_slot;
     272           0 :       incremental_snapshots[ incremental_snapshots_cnt ].base_slot = entry_full_slot;
     273           0 :       incremental_snapshots[ incremental_snapshots_cnt ].is_zstd   = is_zstd;
     274           0 :       FD_TEST( fd_cstr_printf_check( incremental_snapshots[ incremental_snapshots_cnt ].path, PATH_MAX, NULL, "%s/%s", directory, entry->d_name ) );
     275           0 :       incremental_snapshots_cnt++;
     276           0 :     }
     277           0 :   }
     278             : 
     279           0 :   if( FD_UNLIKELY( errno && errno!=ENOENT ) ) FD_LOG_ERR(( "readdir() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     280           0 :   if( FD_UNLIKELY( -1==closedir( dir ) ) ) FD_LOG_ERR(( "closedir() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     281             : 
     282           0 :   if( FD_LIKELY( full_snapshots_cnt>max_full_snapshots_to_keep ) ) {
     283           0 :     sort_ssarchive_entries_inplace( full_snapshots, full_snapshots_cnt );
     284           0 :     for( ulong i=max_full_snapshots_to_keep; i<full_snapshots_cnt; i++ ) {
     285           0 :       if( FD_UNLIKELY( -1==unlink( full_snapshots[ i ].path ) ) ) {
     286           0 :         FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", full_snapshots[ i ].path, errno, fd_io_strerror( errno ) ));
     287           0 :       }
     288           0 :     }
     289           0 :   }
     290             : 
     291           0 :   if( FD_LIKELY( incremental_snapshots_cnt>max_incremental_snapshots_to_keep ) ) {
     292           0 :     sort_ssarchive_entries_inplace( incremental_snapshots, incremental_snapshots_cnt );
     293           0 :     for( ulong i=max_incremental_snapshots_to_keep; i<incremental_snapshots_cnt; i++ ) {
     294           0 :       if( FD_UNLIKELY( -1==unlink( incremental_snapshots[ i ].path ) ) ) {
     295           0 :         FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", incremental_snapshots[ i ].path, errno, fd_io_strerror( errno ) ));
     296           0 :       }
     297           0 :     }
     298           0 :   }
     299           0 : }

Generated by: LCOV version 1.14