LCOV - code coverage report
Current view: top level - app/fdctl - config.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 311 532 58.5 %
Date: 2025-01-08 12:08:44 Functions: 15 18 83.3 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "config.h"
       3             : #include "config_parse.h"
       4             : #include "fdctl.h"
       5             : 
       6             : #include "run/topos/topos.h"
       7             : 
       8             : #include "../../ballet/toml/fd_toml.h"
       9             : #include "../../disco/topo/fd_pod_format.h"
      10             : #include "../../flamenco/runtime/fd_blockstore.h"
      11             : #include "../../flamenco/runtime/fd_txncache.h"
      12             : #include "../../funk/fd_funk.h"
      13             : #include "../../util/net/fd_eth.h"
      14             : #include "../../util/net/fd_ip4.h"
      15             : 
      16             : #include <fcntl.h>
      17             : #include <pwd.h>
      18             : #include <stdio.h>
      19             : #include <stdlib.h>
      20             : #include <unistd.h>
      21             : #include <net/if.h>
      22             : #include <linux/if.h>
      23             : #include <arpa/inet.h>
      24             : #include <sys/mman.h>
      25             : #include <sys/stat.h>
      26             : #include <sys/sysinfo.h>
      27             : #include <sys/ioctl.h>
      28             : #include <sys/utsname.h>
      29             : #include <sys/wait.h>
      30             : 
      31             : void
      32             : replace( char *       in,
      33             :          const char * pat,
      34          18 :          const char * sub ) {
      35          18 :   char * replace = strstr( in, pat );
      36          18 :   if( FD_LIKELY( replace ) ) {
      37           6 :     ulong pat_len = strlen( pat );
      38           6 :     ulong sub_len = strlen( sub );
      39           6 :     ulong in_len  = strlen( in );
      40           6 :     if( FD_UNLIKELY( pat_len > in_len ) ) return;
      41             : 
      42           6 :     ulong total_len = in_len - pat_len + sub_len;
      43           6 :     if( FD_UNLIKELY( total_len >= PATH_MAX ) )
      44           0 :       FD_LOG_ERR(( "configuration scratch directory path too long: `%s`", in ));
      45             : 
      46           6 :     uchar after[PATH_MAX] = {0};
      47           6 :     fd_memcpy( after, replace + pat_len, strlen( replace + pat_len ) );
      48           6 :     fd_memcpy( replace, sub, sub_len );
      49           6 :     ulong after_len = strlen( ( const char * ) after );
      50           6 :     fd_memcpy( replace + sub_len, after, after_len );
      51           6 :     in[ total_len ] = '\0';
      52           6 :   }
      53          18 : }
      54             : 
      55             : static void
      56             : fdctl_cfg_load_buf( config_t *   out,
      57             :                     char const * buf,
      58             :                     ulong        sz,
      59           3 :                     char const * path ) {
      60             : 
      61           3 :   static uchar pod_mem[ 1UL<<30 ];
      62           3 :   uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );
      63             : 
      64           3 :   uchar scratch[ 4096 ];
      65           3 :   long toml_err = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch) );
      66           3 :   if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) {
      67           0 :     FD_LOG_ERR(( "Invalid config (%s)", path ));
      68           0 :   }
      69             : 
      70           3 :   if( FD_UNLIKELY( !fdctl_pod_to_cfg( out, pod ) ) ) {
      71           0 :     FD_LOG_ERR(( "Invalid config (%s)", path ));
      72           0 :   }
      73             : 
      74           3 :   fd_pod_delete( fd_pod_leave( pod ) );
      75           3 : }
      76             : 
      77             : static void
      78             : fdctl_cfg_load_file( config_t *   out,
      79           0 :                      char const * path ) {
      80             : 
      81           0 :   int fd = open( path, O_RDONLY );
      82           0 :   if( FD_UNLIKELY( fd<0 ) ) {
      83           0 :     FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      84           0 :   }
      85             : 
      86           0 :   struct stat st;
      87           0 :   if( FD_UNLIKELY( fstat( fd, &st ) ) ) {
      88           0 :     FD_LOG_ERR(( "fstat(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      89           0 :   }
      90           0 :   ulong toml_sz = (ulong)st.st_size;
      91             : 
      92           0 :   void * mem = mmap( NULL, toml_sz, PROT_READ, MAP_PRIVATE, fd, 0 );
      93           0 :   if( FD_UNLIKELY( mem==MAP_FAILED ) ) {
      94           0 :     FD_LOG_ERR(( "mmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      95           0 :   }
      96             : 
      97           0 :   if( FD_UNLIKELY( 0!=close( fd ) ) ) {
      98           0 :     FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      99           0 :   }
     100             : 
     101           0 :   fdctl_cfg_load_buf( out, mem, toml_sz, path );
     102             : 
     103           0 :   if( FD_UNLIKELY( 0!=munmap( mem, toml_sz ) ) ) {
     104           0 :     FD_LOG_ERR(( "munmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
     105           0 :   }
     106           0 : }
     107             : 
     108             : static uint
     109           3 : listen_address( const char * interface ) {
     110           3 :   int fd = socket( AF_INET, SOCK_DGRAM, 0 );
     111           3 :   struct ifreq ifr = {0};
     112           3 :   ifr.ifr_addr.sa_family = AF_INET;
     113           3 :   strncpy( ifr.ifr_name, interface, IFNAMSIZ );
     114           3 :   ifr.ifr_name[ IFNAMSIZ-1 ] = '\0';
     115           3 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFADDR, &ifr ) ) )
     116           0 :     FD_LOG_ERR(( "could not get IP address of interface `%s` (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
     117           3 :   if( FD_UNLIKELY( close(fd) ) )
     118           0 :     FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
     119           3 :   return ((struct sockaddr_in *)fd_type_pun( &ifr.ifr_addr ))->sin_addr.s_addr;
     120           3 : }
     121             : 
     122             : static void
     123             : mac_address( const char * interface,
     124           3 :              uchar *      mac ) {
     125           3 :   int fd = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP );
     126           3 :   struct ifreq ifr;
     127           3 :   ifr.ifr_addr.sa_family = AF_INET;
     128           3 :   strncpy( ifr.ifr_name, interface, IFNAMSIZ );
     129           3 :   if( FD_UNLIKELY( ioctl( fd, SIOCGIFHWADDR, &ifr ) ) )
     130           0 :     FD_LOG_ERR(( "could not get MAC address of interface `%s`: (%i-%s)", interface, errno, fd_io_strerror( errno ) ));
     131           3 :   if( FD_UNLIKELY( close(fd) ) )
     132           0 :     FD_LOG_ERR(( "could not close socket (%i-%s)", errno, fd_io_strerror( errno ) ));
     133           3 :   fd_memcpy( mac, ifr.ifr_hwaddr.sa_data, 6 );
     134           3 : }
     135             : 
     136             : static void
     137           3 : username_to_id( config_t * config ) {
     138           3 :   uint * results = mmap( NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0 );
     139           3 :   if( FD_UNLIKELY( results==MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     140             : 
     141           3 :   results[ 0 ] = UINT_MAX;
     142           3 :   results[ 1 ] = UINT_MAX;
     143             : 
     144             :   /* This is extremely unfortunate.  We just want to call getpwnam but
     145             :      on various glibc it can open `/var/lib/sss/mc/passwd` and then not
     146             :      close it.  We could go and find this file descriptor and close it
     147             :      for the library, but that is a bit of a hack.  Instead we fork a
     148             :      new process to call getpwnam and then exit.
     149             : 
     150             :      We could try just reading /etc/passwd here instead, but the glibc
     151             :      getpwnam implementation does a lot of things we need, including
     152             :      potentially reading from NCSD or SSSD. */
     153             : 
     154           3 :   pid_t pid = fork();
     155           3 :   if( FD_UNLIKELY( pid == -1 ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     156             : 
     157           3 :   if( FD_LIKELY( !pid ) ) {
     158           0 :     char buf[ 16384 ];
     159           0 :     struct passwd pwd;
     160           0 :     struct passwd * result;
     161           0 :     int error = getpwnam_r( config->user, &pwd, buf, sizeof(buf), &result );
     162           0 :     if( FD_UNLIKELY( error ) ) {
     163           0 :       if( FD_LIKELY( error==ENOENT || error==ESRCH ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
     164           0 :       else FD_LOG_ERR(( "could not get user id for `%s` (%i-%s)", config->user, errno, fd_io_strerror( errno ) ));
     165           0 :     }
     166           0 :     if( FD_UNLIKELY( !result ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
     167           0 :     results[ 0 ] = pwd.pw_uid;
     168           0 :     results[ 1 ] = pwd.pw_gid;
     169           0 :     exit_group( 0 );
     170           3 :   } else {
     171           3 :     int wstatus;
     172           3 :     if( FD_UNLIKELY( waitpid( pid, &wstatus, 0 )==-1 ) ) FD_LOG_ERR(( "waitpid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     173           3 :     if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) )
     174           0 :       FD_LOG_ERR(( "uid fetch process terminated by signal %i-%s", WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
     175           3 :     if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) )
     176           0 :       FD_LOG_ERR(( "uid fetch process exited with status %i", WEXITSTATUS( wstatus ) ));
     177           3 :   }
     178             : 
     179           3 :   if( FD_UNLIKELY( results[ 0 ]==UINT_MAX || results[ 1 ]==UINT_MAX ) ) FD_LOG_ERR(( "could not get user id for `%s`", config->user ));
     180           3 :   config->uid = results[ 0 ];
     181           3 :   config->gid = results[ 1 ];
     182             : 
     183           3 :   if( FD_UNLIKELY( munmap( results, 4096 ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     184           3 : }
     185             : 
     186             : FD_FN_CONST fd_topo_run_tile_t *
     187         207 : fd_topo_tile_to_config( fd_topo_tile_t const * tile ) {
     188         207 :   fd_topo_run_tile_t ** run = TILES;
     189        1404 :   while( *run ) {
     190        1404 :     if( FD_LIKELY( !strcmp( (*run)->name, tile->name ) ) ) return *run;
     191        1197 :     run++;
     192        1197 :   }
     193           0 :   FD_LOG_ERR(( "unknown tile name `%s`", tile->name ));
     194           0 : }
     195             : 
     196             : ulong
     197             : fdctl_obj_align( fd_topo_t const *     topo,
     198         495 :                  fd_topo_obj_t const * obj ) {
     199         495 :   if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
     200          69 :     fd_topo_tile_t const * tile = NULL;
     201         828 :     for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     202         828 :       if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
     203          69 :         tile = &topo->tiles[ i ];
     204          69 :         break;
     205          69 :       }
     206         828 :     }
     207          69 :     fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
     208          69 :     if( FD_LIKELY( config->scratch_align ) ) return config->scratch_align();
     209           0 :     return 1UL;
     210         426 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "mcache" ) ) ) {
     211          99 :     return fd_mcache_align();
     212         327 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "dcache" ) ) ) {
     213          99 :     return fd_dcache_align();
     214         228 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "cnc" ) ) ) {
     215           0 :     return fd_cnc_align();
     216         228 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "fseq" ) ) ) {
     217         159 :     return fd_fseq_align();
     218         159 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "metrics" ) ) ) {
     219          69 :     return FD_METRICS_ALIGN;
     220          69 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "blockstore" ) ) ) {
     221           0 :     return fd_blockstore_align();
     222           0 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) {
     223           0 :     return fd_funk_align();
     224           0 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) {
     225           0 :     return fd_txncache_align();
     226           0 :   } else {
     227           0 :     FD_LOG_ERR(( "unknown object `%s`", obj->name ));
     228           0 :     return 0UL;
     229           0 :   }
     230         495 : }
     231             : 
     232             : ulong
     233             : fdctl_obj_footprint( fd_topo_t const *     topo,
     234         495 :                      fd_topo_obj_t const * obj ) {
     235         495 :   #define VAL(name) (__extension__({                                                               \
     236         396 :       ulong __x = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, name );      \
     237         396 :       if( FD_UNLIKELY( __x==ULONG_MAX ) ) FD_LOG_ERR(( "obj.%lu.%s was not set", obj->id, name )); \
     238         396 :       __x; }))
     239             : 
     240         495 :   if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
     241          69 :     fd_topo_tile_t const * tile = NULL;
     242         828 :     for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     243         828 :       if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
     244          69 :         tile = &topo->tiles[ i ];
     245          69 :         break;
     246          69 :       }
     247         828 :     }
     248          69 :     fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
     249          69 :     if( FD_LIKELY( config->scratch_footprint ) ) return config->scratch_footprint( tile );
     250           0 :     return 0UL;
     251         426 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "mcache" ) ) ) {
     252          99 :     return fd_mcache_footprint( VAL("depth"), 0UL );
     253         327 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "dcache" ) ) ) {
     254          99 :     return fd_dcache_footprint( fd_dcache_req_data_sz( VAL("mtu"), VAL("depth"), VAL("burst"), 1), 0UL );
     255         228 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "cnc" ) ) ) {
     256           0 :     return fd_cnc_footprint( 0UL );
     257         228 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "fseq" ) ) ) {
     258         159 :     return fd_fseq_footprint();
     259         159 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "metrics" ) ) ) {
     260          69 :     return FD_METRICS_FOOTPRINT( VAL("in_cnt"), VAL("cons_cnt") );
     261          69 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "blockstore" ) ) ) {
     262           0 :     return fd_blockstore_footprint( VAL("shred_max"), VAL("block_max"), VAL("idx_max"), VAL("txn_max") ) + VAL("alloc_max");
     263           0 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) {
     264           0 :     return fd_funk_footprint();
     265           0 :   } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) {
     266           0 :     return fd_txncache_footprint( VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot"), FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS );
     267           0 :   } else {
     268           0 :     FD_LOG_ERR(( "unknown object `%s`", obj->name ));
     269           0 :     return 0UL;
     270           0 :   }
     271         495 : #undef VAL
     272         495 : }
     273             : 
     274             : ulong
     275             : fdctl_obj_loose( fd_topo_t const *     topo,
     276         495 :                  fd_topo_obj_t const * obj ) {
     277         495 :   ulong loose = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, "loose" );
     278         495 :   if( loose!=ULONG_MAX ) {
     279           0 :     return loose;
     280           0 :   }
     281             : 
     282         495 :   if( FD_UNLIKELY( !strcmp( obj->name, "tile" ) ) ) {
     283          69 :     fd_topo_tile_t const * tile = NULL;
     284         828 :     for( ulong i=0UL; i<topo->tile_cnt; i++ ) {
     285         828 :       if( FD_LIKELY( topo->tiles[ i ].tile_obj_id==obj->id ) ) {
     286          69 :         tile = &topo->tiles[ i ];
     287          69 :         break;
     288          69 :       }
     289         828 :     }
     290          69 :     fd_topo_run_tile_t * config = fd_topo_tile_to_config( tile );
     291          69 :     if( FD_LIKELY( config->loose_footprint ) ) return config->loose_footprint( tile );
     292          69 :   }
     293         492 :   return 0UL;
     294         495 : }
     295             : 
     296             : fd_topo_run_tile_t
     297           0 : fdctl_tile_run( fd_topo_tile_t * tile ) {
     298           0 :   return *fd_topo_tile_to_config( tile );
     299           0 : }
     300             : 
     301             : 
     302             : static void
     303           3 : validate_ports( config_t * result ) {
     304           3 :   char dynamic_port_range[ 32 ];
     305           3 :   fd_memcpy( dynamic_port_range, result->dynamic_port_range, sizeof(dynamic_port_range) );
     306             : 
     307           3 :   char * dash = strstr( dynamic_port_range, "-" );
     308           3 :   if( FD_UNLIKELY( !dash ) )
     309           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     310           3 :                  "This must be formatted like `<min>-<max>`",
     311           3 :                  result->dynamic_port_range ));
     312             : 
     313           3 :   *dash = '\0';
     314           3 :   char * endptr;
     315           3 :   ulong agave_port_min = strtoul( dynamic_port_range, &endptr, 10 );
     316           3 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_min > USHORT_MAX ) )
     317           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     318           3 :                  "This must be formatted like `<min>-<max>`",
     319           3 :                  result->dynamic_port_range ));
     320           3 :   ulong agave_port_max = strtoul( dash + 1, &endptr, 10 );
     321           3 :   if( FD_UNLIKELY( *endptr != '\0' || agave_port_max > USHORT_MAX ) )
     322           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     323           3 :                  "This must be formatted like `<min>-<max>`",
     324           3 :                  result->dynamic_port_range ));
     325           3 :   if( FD_UNLIKELY( agave_port_min > agave_port_max ) )
     326           0 :     FD_LOG_ERR(( "configuration specifies invalid [dynamic_port_range] `%s`. "
     327           3 :                  "The minimum port must be less than or equal to the maximum port",
     328           3 :                  result->dynamic_port_range ));
     329             : 
     330           3 :   if( FD_UNLIKELY( result->tiles.quic.regular_transaction_listen_port >= agave_port_min &&
     331           3 :                    result->tiles.quic.regular_transaction_listen_port < agave_port_max ) )
     332           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.transaction_listen_port] `%hu`. "
     333           3 :                  "This must be outside the dynamic port range `%s`",
     334           3 :                  result->tiles.quic.regular_transaction_listen_port,
     335           3 :                  result->dynamic_port_range ));
     336             : 
     337           3 :   if( FD_UNLIKELY( result->tiles.quic.quic_transaction_listen_port >= agave_port_min &&
     338           3 :                    result->tiles.quic.quic_transaction_listen_port < agave_port_max ) )
     339           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     340           3 :                  "This must be outside the dynamic port range `%s`",
     341           3 :                  result->tiles.quic.quic_transaction_listen_port,
     342           3 :                  result->dynamic_port_range ));
     343             : 
     344           3 :   if( FD_UNLIKELY( result->tiles.shred.shred_listen_port >= agave_port_min &&
     345           3 :                    result->tiles.shred.shred_listen_port < agave_port_max ) )
     346           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.shred.shred_listen_port] `%hu`. "
     347           3 :                  "This must be outside the dynamic port range `%s`",
     348           3 :                  result->tiles.shred.shred_listen_port,
     349           3 :                  result->dynamic_port_range ));
     350           3 : }
     351             : 
     352             : /* These CLUSTER_* values must be ordered from least important to most
     353             :    important network.  Eg, it's important that if a config has the
     354             :    MAINNET_BETA genesis hash, but has a bunch of entrypoints that we
     355             :    recognize as TESTNET, we classify it as MAINNET_BETA so we can be
     356             :    maximally restrictive.  This is done by a high-to-low comparison. */
     357           9 : #define FD_CONFIG_CLUSTER_UNKNOWN      (0UL)
     358           0 : #define FD_CONFIG_CLUSTER_PYTHTEST     (1UL)
     359           0 : #define FD_CONFIG_CLUSTER_TESTNET      (2UL)
     360           0 : #define FD_CONFIG_CLUSTER_DEVNET       (3UL)
     361           0 : #define FD_CONFIG_CLUSTER_PYTHNET      (4UL)
     362           0 : #define FD_CONFIG_CLUSTER_MAINNET_BETA (5UL)
     363             : 
     364             : FD_FN_PURE static ulong
     365           3 : determine_cluster( char * expected_genesis_hash ) {
     366           3 :   char const * DEVNET_GENESIS_HASH = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
     367           3 :   char const * TESTNET_GENESIS_HASH = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY";
     368           3 :   char const * MAINNET_BETA_GENESIS_HASH = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
     369           3 :   char const * PYTHTEST_GENESIS_HASH = "EkCkB7RWVrgkcpariRpd3pjf7GwiCMZaMHKUpB5Na1Ve";
     370           3 :   char const * PYTHNET_GENESIS_HASH = "GLKkBUr6r72nBtGrtBPJLRqtsh8wXZanX4xfnqKnWwKq";
     371             : 
     372           3 :   ulong cluster = FD_CONFIG_CLUSTER_UNKNOWN;
     373           3 :   if( FD_LIKELY( expected_genesis_hash ) ) {
     374           3 :     if( FD_UNLIKELY( !strcmp( expected_genesis_hash, DEVNET_GENESIS_HASH ) ) )            cluster = FD_CONFIG_CLUSTER_DEVNET;
     375           3 :     else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, TESTNET_GENESIS_HASH ) ) )      cluster = FD_CONFIG_CLUSTER_TESTNET;
     376           3 :     else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, MAINNET_BETA_GENESIS_HASH ) ) ) cluster = FD_CONFIG_CLUSTER_MAINNET_BETA;
     377           3 :     else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHTEST_GENESIS_HASH ) ) )     cluster = FD_CONFIG_CLUSTER_PYTHTEST;
     378           3 :     else if( FD_UNLIKELY( !strcmp( expected_genesis_hash, PYTHNET_GENESIS_HASH ) ) )      cluster = FD_CONFIG_CLUSTER_PYTHNET;
     379           3 :   }
     380             : 
     381           3 :   return cluster;
     382           3 : }
     383             : 
     384             : FD_FN_CONST static int
     385           9 : parse_log_level( char const * level ) {
     386           9 :   if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) )    return 0;
     387           9 :   if( FD_UNLIKELY( !strcmp( level, "INFO"  ) ) )    return 1;
     388           6 :   if( FD_UNLIKELY( !strcmp( level, "NOTICE"  ) ) )  return 2;
     389           3 :   if( FD_UNLIKELY( !strcmp( level, "WARNING"  ) ) ) return 3;
     390           0 :   if( FD_UNLIKELY( !strcmp( level, "ERR" ) ) )      return 4;
     391           0 :   if( FD_UNLIKELY( !strcmp( level, "CRIT" ) ) )     return 5;
     392           0 :   if( FD_UNLIKELY( !strcmp( level, "ALERT" ) ) )    return 6;
     393           0 :   if( FD_UNLIKELY( !strcmp( level, "EMERG" ) ) )    return 7;
     394           0 :   return -1;
     395           0 : }
     396             : 
     397             : FD_FN_CONST static char *
     398           3 : cluster_to_cstr( ulong cluster ) {
     399           3 :   switch( cluster ) {
     400           3 :     case FD_CONFIG_CLUSTER_UNKNOWN:      return "unknown";
     401           0 :     case FD_CONFIG_CLUSTER_PYTHTEST:     return "pythtest";
     402           0 :     case FD_CONFIG_CLUSTER_TESTNET:      return "testnet";
     403           0 :     case FD_CONFIG_CLUSTER_DEVNET:       return "devnet";
     404           0 :     case FD_CONFIG_CLUSTER_PYTHNET:      return "pythnet";
     405           0 :     case FD_CONFIG_CLUSTER_MAINNET_BETA: return "mainnet-beta";
     406           0 :     default:                             return "unknown";
     407           3 :   }
     408           3 : }
     409             : 
     410             : static char *
     411           3 : default_user( void ) {
     412           3 :   char * name = getenv( "SUDO_USER" );
     413           3 :   if( FD_UNLIKELY( name ) ) return name;
     414             : 
     415           0 :   name = getenv( "LOGNAME" );
     416           0 :   if( FD_LIKELY( name ) ) return name;
     417             : 
     418           0 :   name = getenv( "USER" );
     419           0 :   if( FD_LIKELY( name ) ) return name;
     420             : 
     421           0 :   name = getenv( "LNAME" );
     422           0 :   if( FD_LIKELY( name ) ) return name;
     423             : 
     424           0 :   name = getenv( "USERNAME" );
     425           0 :   if( FD_LIKELY( name ) ) return name;
     426             : 
     427           0 :   name = getlogin();
     428           0 :   if( FD_UNLIKELY( !name && (errno==ENXIO || errno==ENOTTY) ) ) return NULL;
     429           0 :   else if( FD_UNLIKELY( !name ) ) FD_LOG_ERR(( "getlogin failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     430           0 :   return name;
     431           0 : }
     432             : 
     433             : void
     434             : fdctl_cfg_from_env( int *      pargc,
     435             :                     char ***   pargv,
     436           3 :                     config_t * config ) {
     437             : 
     438           3 :   memset( config, 0, sizeof(config_t) );
     439             : #if FD_HAS_NO_AGAVE
     440             :   static uchar pod_mem1[ 1UL<<20 ];
     441             :   static uchar pod_mem2[ 1UL<<20 ];
     442             :   uchar * pod1 = fd_pod_join( fd_pod_new( pod_mem1, sizeof(pod_mem1) ) );
     443             :   uchar * pod2 = fd_pod_join( fd_pod_new( pod_mem2, sizeof(pod_mem2) ) );
     444             : 
     445             :   uchar scratch[ 4096 ];
     446             :   long toml_err = fd_toml_parse( fdctl_default_config, fdctl_default_config_sz, pod1, scratch, sizeof(scratch) );
     447             :   if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) FD_LOG_ERR(( "Invalid config (%s)", "default.toml" ));
     448             :   toml_err = fd_toml_parse( fdctl_default_firedancer_config, fdctl_default_firedancer_config_sz, pod2, scratch, sizeof(scratch) );
     449             :   if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) FD_LOG_ERR(( "Invalid config (%s)", "default-firedancer.toml" ));
     450             : 
     451             :   if( FD_UNLIKELY( !fdctl_pod_to_cfg( config, pod1 ) ) ) FD_LOG_ERR(( "Invalid config (%s)", "default.toml" ));
     452             :   if( FD_UNLIKELY( !fdctl_pod_to_cfg( config, pod2 ) ) ) FD_LOG_ERR(( "Invalid config (%s)", "default-firedancer.toml" ));
     453             :   fd_pod_delete( fd_pod_leave( pod1 ) );
     454             :   fd_pod_delete( fd_pod_leave( pod2 ) );
     455             : #else
     456           3 :   fdctl_cfg_load_buf( config, (char const *)fdctl_default_config, fdctl_default_config_sz, "default" );
     457           3 : #endif
     458             : 
     459           3 :   const char * user_config = fd_env_strip_cmdline_cstr(
     460           3 :       pargc,
     461           3 :       pargv,
     462           3 :       "--config",
     463           3 :       "FIREDANCER_CONFIG_TOML",
     464           3 :       NULL );
     465             : 
     466           3 :   if( FD_LIKELY( user_config ) ) {
     467           0 :     fdctl_cfg_load_file( config, user_config );
     468           0 :   }
     469             : 
     470           3 :   int netns = fd_env_strip_cmdline_contains( pargc, pargv, "--netns" );
     471           3 :   if( FD_UNLIKELY( netns ) ) {
     472           0 :     config->development.netns.enabled = 1;
     473           0 :     strncpy( config->tiles.net.interface,
     474           0 :              config->development.netns.interface0,
     475           0 :              sizeof(config->tiles.net.interface) );
     476           0 :     config->tiles.net.interface[ sizeof(config->tiles.net.interface) - 1 ] = '\0';
     477           0 :   }
     478             : 
     479           3 :   if( FD_UNLIKELY( !strcmp( config->user, "" ) ) ) {
     480           3 :     const char * user = default_user();
     481           3 :     if( FD_UNLIKELY( !user ) ) FD_LOG_ERR(( "could not automatically determine a user to run Firedancer as. You must specify a [user] in your configuration TOML file." ));
     482           3 :     if( FD_UNLIKELY( strlen( user ) >= sizeof( config->user ) ) )
     483           0 :                               FD_LOG_ERR(( "user name `%s` is too long", user ));
     484           3 :     strncpy( config->user, user, 256 );
     485           3 :   }
     486             : 
     487           3 :   struct utsname utsname;
     488           3 :   if( FD_UNLIKELY( -1==uname( &utsname ) ) )
     489           0 :     FD_LOG_ERR(( "could not get uname (%i-%s)", errno, fd_io_strerror( errno ) ));
     490           3 :   strncpy( config->hostname, utsname.nodename, sizeof(config->hostname) );
     491           3 :   config->hostname[ sizeof(config->hostname)-1UL ] = '\0'; /* Just truncate the name if it's too long to fit */
     492             : 
     493           3 :   if( FD_UNLIKELY( !strcmp( config->tiles.net.interface, "" ) && !config->development.netns.enabled ) ) {
     494           3 :     int ifindex = internet_routing_interface();
     495           3 :     if( FD_UNLIKELY( ifindex == -1 ) )
     496           0 :       FD_LOG_ERR(( "no network device found which routes to 8.8.8.8. If no network "
     497           3 :                    "interface is specified in the configuration file, Firedancer "
     498           3 :                    "tries to use the first network interface found which routes to "
     499           3 :                    "8.8.8.8. You can see what this is by running `ip route get 8.8.8.8` "
     500           3 :                    "You can fix this error by specifying a network interface to bind to in "
     501           3 :                    "your configuration file under [net.interface]" ));
     502             : 
     503           3 :     if( FD_UNLIKELY( !if_indextoname( (uint)ifindex, config->tiles.net.interface ) ) )
     504           0 :       FD_LOG_ERR(( "could not get name of interface with index %d", ifindex ));
     505           3 :   }
     506             : 
     507           3 :   ulong cluster = determine_cluster( config->consensus.expected_genesis_hash );
     508           3 :   config->is_live_cluster = cluster != FD_CONFIG_CLUSTER_UNKNOWN;
     509             : 
     510           3 :   if( FD_UNLIKELY( config->development.netns.enabled ) ) {
     511             :     /* not currently supporting multihoming on netns */
     512           0 :     if( FD_UNLIKELY( strcmp( config->development.netns.interface0, config->tiles.net.interface ) ) )
     513           0 :       FD_LOG_ERR(( "netns interface and firedancer interface are different. If you are using the "
     514           0 :                    "[development.netns] functionality to run Firedancer in a network namespace "
     515           0 :                    "for development, the configuration file must specify that "
     516           0 :                    "[development.netns.interface0] is the same as [net.interface]" ));
     517             : 
     518           0 :     if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->development.netns.interface0_addr, &config->tiles.net.ip_addr ) ) )
     519           0 :       FD_LOG_ERR(( "configuration specifies invalid netns IP address `%s`", config->development.netns.interface0_addr ));
     520           0 :     if( FD_UNLIKELY( !fd_cstr_to_mac_addr( config->development.netns.interface0_mac, config->tiles.net.mac_addr ) ) )
     521           0 :       FD_LOG_ERR(( "configuration specifies invalid netns MAC address `%s`", config->development.netns.interface0_mac ));
     522           3 :   } else {
     523           3 :     if( FD_UNLIKELY( !if_nametoindex( config->tiles.net.interface ) ) )
     524           0 :       FD_LOG_ERR(( "configuration specifies network interface `%s` which does not exist", config->tiles.net.interface ));
     525           3 :     uint iface_ip = listen_address( config->tiles.net.interface );
     526           3 :     if( FD_UNLIKELY( strcmp( config->gossip.host, "" ) ) ) {
     527           0 :       uint gossip_ip_addr = iface_ip;
     528           0 :       int  has_gossip_ip4 = 0;
     529           0 :       if( FD_UNLIKELY( strlen( config->gossip.host )<=15UL ) ) {
     530             :         /* Only sets gossip_ip_addr if it's a valid IPv4 address, otherwise assume it's a DNS name */
     531           0 :         has_gossip_ip4 = fd_cstr_to_ip4_addr( config->gossip.host, &gossip_ip_addr );
     532           0 :       }
     533           0 :       if ( FD_UNLIKELY( !fd_ip4_addr_is_public( gossip_ip_addr ) && config->is_live_cluster && has_gossip_ip4 ) )
     534           0 :         FD_LOG_ERR(( "Trying to use [gossip.host] " FD_IP4_ADDR_FMT " for listening to incoming "
     535           0 :                      "transactions, but it is part of a private network and will not be routable "
     536           0 :                      "for other Solana network nodes.",
     537           0 :                      FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
     538           3 :     } else if ( FD_UNLIKELY( !fd_ip4_addr_is_public( iface_ip ) && config->is_live_cluster ) ) {
     539           0 :       FD_LOG_ERR(( "Trying to use network interface `%s` for listening to incoming transactions, "
     540           0 :                    "but it has IPv4 address " FD_IP4_ADDR_FMT " which is part of a private network "
     541           0 :                    "and will not be routable for other Solana network nodes. If you are running "
     542           0 :                    "behind a NAT and this interface is publicly reachable, you can continue by "
     543           0 :                    "manually specifying the IP address to advertise in your configuration under "
     544           0 :                    "[gossip.host].",
     545           0 :                    config->tiles.net.interface, FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
     546           0 :     }
     547             : 
     548           3 :     config->tiles.net.ip_addr = iface_ip;
     549           3 :     mac_address( config->tiles.net.interface, config->tiles.net.mac_addr );
     550             : 
     551             :     /* support for multihomed hosts */
     552           3 :     ulong multi_cnt = config->tiles.net.multihome_ip_addrs_cnt;
     553           3 :     for( ulong j = 0; j < multi_cnt; ++j ) {
     554           0 :       int success = fd_cstr_to_ip4_addr( config->tiles.net.multihome_ip_addrs[j],
     555           0 :           &config->tiles.net.multihome_ip4_addrs[j] );
     556           0 :       if( !success ) {
     557           0 :         FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
     558           0 :                      "specifies malformed IP address `%s`",
     559           0 :                      config->tiles.net.multihome_ip_addrs[j] ));
     560           0 :       }
     561           0 :     }
     562             : 
     563             :     /* look for duplicate addresses */
     564             :     /* there's only a few, so do the O(n^2) comparison */
     565           3 :     for( ulong j = 0; j < multi_cnt; ++j ) {
     566           0 :       if( config->tiles.net.ip_addr == config->tiles.net.multihome_ip4_addrs[j] ) {
     567           0 :         FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
     568           0 :                      "specifies an address that matches [tiles.net.src_ip_addr]" ));
     569           0 :       }
     570           0 :       for( ulong k = j+1; k < multi_cnt; ++k ) {
     571           0 :         if( config->tiles.net.multihome_ip4_addrs[j] == config->tiles.net.multihome_ip4_addrs[k] ) {
     572           0 :           FD_LOG_ERR(( "configuration option [tiles.net.multihome_ip_addrs] "
     573           0 :                        "specifies duplicate ip addresses `%s`",
     574           0 :                        config->tiles.net.multihome_ip_addrs[j] ));
     575           0 :         }
     576           0 :       }
     577           0 :     }
     578             : 
     579           3 :   }
     580             : 
     581           3 :   username_to_id( config );
     582             : 
     583           3 :   if( config->uid == 0 || config->gid == 0 )
     584           0 :     FD_LOG_ERR(( "firedancer cannot run as root. please specify a non-root user in the configuration file" ));
     585             : 
     586           3 :   if( FD_UNLIKELY( getuid() != 0 && config->uid != getuid() ) )
     587           0 :     FD_LOG_ERR(( "running as uid %u, but config specifies uid %u", getuid(), config->uid ));
     588           3 :   if( FD_UNLIKELY( getgid() != 0 && config->gid != getgid() ) )
     589           0 :     FD_LOG_ERR(( "running as gid %u, but config specifies gid %u", getgid(), config->gid ));
     590             : 
     591           3 :   ulong len = strlen( config->hugetlbfs.mount_path );
     592           3 :   if( FD_UNLIKELY( !len ) ) FD_LOG_ERR(( "[hugetlbfs.mount_path] must be non-empty in your configuration file" ));
     593           3 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.gigantic_page_mount_path,
     594           3 :                                  sizeof(config->hugetlbfs.gigantic_page_mount_path),
     595           3 :                                  NULL,
     596           3 :                                  "%s/.gigantic",
     597           3 :                                  config->hugetlbfs.mount_path ) );
     598           3 :   FD_TEST( fd_cstr_printf_check( config->hugetlbfs.huge_page_mount_path,
     599           3 :                                  sizeof(config->hugetlbfs.huge_page_mount_path),
     600           3 :                                  NULL,
     601           3 :                                  "%s/.huge",
     602           3 :                                  config->hugetlbfs.mount_path ) );
     603             : 
     604           3 :   replace( config->log.path, "{user}", config->user );
     605           3 :   replace( config->log.path, "{name}", config->name );
     606           3 :   if( FD_LIKELY( !strcmp( "auto", config->log.colorize ) ) )       config->log.colorize1 = 2;
     607           0 :   else if( FD_LIKELY( !strcmp( "true", config->log.colorize ) ) )  config->log.colorize1 = 1;
     608           0 :   else if( FD_LIKELY( !strcmp( "false", config->log.colorize ) ) ) config->log.colorize1 = 0;
     609           0 :   else FD_LOG_ERR(( "[log.colorize] must be one of \"auto\", \"true\", or \"false\"" ));
     610             : 
     611           3 :   if( FD_LIKELY( 2==config->log.colorize1 ) ) {
     612           3 :     char const * cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "COLORTERM", NULL );
     613           3 :     int truecolor = cstr && !strcmp( cstr, "truecolor" );
     614             : 
     615           3 :     cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "TERM", NULL );
     616           3 :     int xterm256color = cstr && !strcmp( cstr, "xterm-256color" );
     617             : 
     618           3 :     config->log.colorize1 = truecolor || xterm256color;
     619           3 :   }
     620             : 
     621           3 :   config->log.level_logfile1 = parse_log_level( config->log.level_logfile );
     622           3 :   config->log.level_stderr1  = parse_log_level( config->log.level_stderr );
     623           3 :   config->log.level_flush1   = parse_log_level( config->log.level_flush );
     624           3 :   if( FD_UNLIKELY( -1==config->log.level_logfile1 ) ) FD_LOG_ERR(( "unrecognized [log.level_logfile] `%s`", config->log.level_logfile ));
     625           3 :   if( FD_UNLIKELY( -1==config->log.level_stderr1 ) )  FD_LOG_ERR(( "unrecognized [log.level_stderr] `%s`", config->log.level_logfile ));
     626           3 :   if( FD_UNLIKELY( -1==config->log.level_flush1 ) )   FD_LOG_ERR(( "unrecognized [log.level_flush] `%s`", config->log.level_logfile ));
     627             : 
     628           3 :   replace( config->scratch_directory, "{user}", config->user );
     629           3 :   replace( config->scratch_directory, "{name}", config->name );
     630             : 
     631           3 :   if( FD_UNLIKELY( strcmp( config->ledger.path, "" ) ) ) {
     632           0 :     replace( config->ledger.path, "{user}", config->user );
     633           0 :     replace( config->ledger.path, "{name}", config->name );
     634           3 :   } else {
     635           3 :     FD_TEST( fd_cstr_printf_check( config->ledger.path, sizeof(config->ledger.path), NULL, "%s/ledger", config->scratch_directory ) );
     636           3 :   }
     637             : 
     638           3 :   if( FD_UNLIKELY( strcmp( config->snapshots.path, "" ) ) ) {
     639           0 :     replace( config->snapshots.path, "{user}", config->user );
     640           0 :     replace( config->snapshots.path, "{name}", config->name );
     641           3 :   } else {
     642           3 :     strncpy( config->snapshots.path, config->ledger.path, sizeof(config->snapshots.path) );
     643           3 :   }
     644             : 
     645           3 :   if( FD_UNLIKELY( !strcmp( config->consensus.identity_path, "" ) ) ) {
     646           3 :     FD_TEST( fd_cstr_printf_check( config->consensus.identity_path,
     647           3 :                                    sizeof(config->consensus.identity_path),
     648           3 :                                    NULL,
     649           3 :                                    "%s/identity.json",
     650           3 :                                    config->scratch_directory ) );
     651           3 :   } else {
     652           0 :     replace( config->consensus.identity_path, "{user}", config->user );
     653           0 :     replace( config->consensus.identity_path, "{name}", config->name );
     654           0 :   }
     655             : 
     656             : #if FD_HAS_NO_AGAVE
     657             :   if( FD_UNLIKELY( !strcmp( config->consensus.vote_account_path, "" ) ) ) {
     658             :     FD_TEST( fd_cstr_printf_check( config->consensus.vote_account_path,
     659             :                                    sizeof(config->consensus.vote_account_path),
     660             :                                    NULL,
     661             :                                    "%s/vote-account.json",
     662             :                                    config->scratch_directory ) );
     663             :   }
     664             : #endif
     665           3 :   replace( config->consensus.vote_account_path, "{user}", config->user );
     666           3 :   replace( config->consensus.vote_account_path, "{name}", config->name );
     667             : 
     668           3 :   for( ulong i=0UL; i<config->consensus.authorized_voter_paths_cnt; i++ ) {
     669           0 :     replace( config->consensus.authorized_voter_paths[ i ], "{user}", config->user );
     670           0 :     replace( config->consensus.authorized_voter_paths[ i ], "{name}", config->name );
     671           0 :   }
     672             : 
     673           3 :   strcpy( config->cluster, cluster_to_cstr( cluster ) );
     674             : 
     675             : #ifdef FD_HAS_NO_AGAVE
     676             :   if( FD_UNLIKELY( config->is_live_cluster && cluster!=FD_CONFIG_CLUSTER_TESTNET ) )
     677             :     FD_LOG_ERR(( "Attempted to start against live cluster `%s`. Firedancer is not "
     678             :                  "ready for production deployment, has not been tested, and is "
     679             :                  "missing consensus critical functionality. Joining a live Solana "
     680             :                  "cluster may destabilize the network. Please do not attempt. You "
     681             :                  "can start against the testnet cluster by specifying the testnet "
     682             :                  "entrypoints from https://docs.solana.com/clusters under "
     683             :                  "[gossip.entrypoints] in your configuration file.", cluster_to_cstr( cluster ) ));
     684             : #endif
     685             : 
     686           3 :   if( FD_LIKELY( config->is_live_cluster) ) {
     687           0 :     if( FD_UNLIKELY( !config->development.sandbox ) )
     688           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration disables the sandbox which is a a development only feature" ));
     689           0 :     if( FD_UNLIKELY( config->development.no_clone ) )
     690           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration disables multiprocess which is a development only feature" ));
     691           0 :     if( FD_UNLIKELY( config->development.netns.enabled ) )
     692           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.netns] which is a development only feature" ));
     693           0 :     if( FD_UNLIKELY( config->development.bench.larger_max_cost_per_block ) )
     694           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.larger_max_cost_per_block] which is a development only feature" ));
     695           0 :     if( FD_UNLIKELY( config->development.bench.larger_shred_limits_per_block ) )
     696           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.larger_shred_limits_per_block] which is a development only feature" ));
     697           0 :     if( FD_UNLIKELY( config->development.bench.disable_blockstore_from_slot ) )
     698           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration has a non-zero value for [development.bench.disable_blockstore_from_slot] which is a development only feature" ));
     699           0 :     if( FD_UNLIKELY( config->development.bench.disable_status_cache ) )
     700           0 :       FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.disable_status_cache] which is a development only feature" ));
     701           0 :   }
     702             : 
     703           3 :   if( FD_UNLIKELY( config->tiles.quic.quic_transaction_listen_port != config->tiles.quic.regular_transaction_listen_port + 6 ) )
     704           0 :     FD_LOG_ERR(( "configuration specifies invalid [tiles.quic.quic_transaction_listen_port] `%hu`. "
     705           3 :                  "This must be 6 more than [tiles.quic.regular_transaction_listen_port] `%hu`",
     706           3 :                  config->tiles.quic.quic_transaction_listen_port,
     707           3 :                  config->tiles.quic.regular_transaction_listen_port ));
     708             : 
     709           3 :   if( FD_LIKELY( !strcmp( config->consensus.identity_path, "" ) ) ) {
     710           0 :     if( FD_UNLIKELY( config->is_live_cluster ) )
     711           0 :       FD_LOG_ERR(( "configuration file must specify [consensus.identity_path] when joining a live cluster" ));
     712             : 
     713           0 :     FD_TEST( fd_cstr_printf_check( config->consensus.identity_path,
     714           0 :                                    sizeof(config->consensus.identity_path),
     715           0 :                                    NULL,
     716           0 :                                    "%s/identity.json",
     717           0 :                                    config->scratch_directory ) );
     718           0 :   }
     719             : 
     720           3 :   fdctl_cfg_validate( config );
     721           3 :   validate_ports( config );
     722           3 :   fd_topo_initialize( config );
     723           3 : }
     724             : 
     725             : int
     726           0 : fdctl_cfg_to_memfd( config_t * config ) {
     727           0 :   int config_memfd = memfd_create( "fd_config", 0 );
     728           0 :   if( FD_UNLIKELY( -1==config_memfd ) ) FD_LOG_ERR(( "memfd_create() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     729           0 :   if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "ftruncate() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     730             : 
     731           0 :   uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ | PROT_WRITE, MAP_SHARED, config_memfd, 0 );
     732           0 :   if( FD_UNLIKELY( bytes == MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     733           0 :   fd_memcpy( bytes, config, sizeof( config_t ) );
     734           0 :   if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     735             : 
     736           0 :   return config_memfd;
     737           0 : }

Generated by: LCOV version 1.14