From d11a36b79c8ad7b5a7c6cc9962467da043768225 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Mon, 24 Jun 2024 20:45:15 +0000 Subject: [PATCH] fdctl: wire up new toml parser --- src/app/fdctl/Local.mk | 3 +- src/app/fdctl/config.c | 492 ++++-------------------------- src/app/fdctl/config.h | 16 +- src/app/fdctl/config_parse.c | 362 ++++++++++++++++++++++ src/app/fdctl/config_parse.h | 20 ++ src/app/fdctl/fuzz_fdctl_config.c | 33 ++ src/app/fdctl/main1.c | 2 +- src/app/fdctl/run/run.c | 2 +- src/app/fddev/tests/test_fddev.c | 2 +- 9 files changed, 488 insertions(+), 444 deletions(-) create mode 100644 src/app/fdctl/config_parse.c create mode 100644 src/app/fdctl/config_parse.h create mode 100644 src/app/fdctl/fuzz_fdctl_config.c diff --git a/src/app/fdctl/Local.mk b/src/app/fdctl/Local.mk index 2de0784ec2..a5f18c9208 100644 --- a/src/app/fdctl/Local.mk +++ b/src/app/fdctl/Local.mk @@ -18,9 +18,10 @@ $(OBJDIR)/obj/app/fdctl/version.d: src/app/fdctl/version.h .PHONY: fdctl cargo-validator cargo-solana rust solana check-solana-hash # fdctl core -$(call add-objs,main1 config caps utility keys ready mem spy help version,fd_fdctl) +$(call add-objs,main1 config config_parse caps utility keys ready mem spy help version,fd_fdctl) $(call add-objs,run/run run/run1 run/run_solana run/topos/topos,fd_fdctl) $(call add-objs,monitor/monitor monitor/helper,fd_fdctl) +$(call make-fuzz-test,fuzz_fdctl_config,fuzz_fdctl_config,fd_fdctl fd_ballet fd_util) # fdctl tiles $(call add-objs,run/tiles/fd_net,fd_fdctl) diff --git a/src/app/fdctl/config.c b/src/app/fdctl/config.c index 66c7ae14ba..3be81c211f 100644 --- a/src/app/fdctl/config.c +++ b/src/app/fdctl/config.c @@ -1,14 +1,18 @@ #define _GNU_SOURCE +#include "config.h" +#include "config_parse.h" #include "fdctl.h" #include "run/run.h" +#include "../../ballet/toml/fd_toml.h" #include "../../disco/topo/fd_topob.h" #include "../../disco/topo/fd_pod_format.h" #include "../../util/net/fd_eth.h" #include "../../util/net/fd_ip4.h" #include "../../util/tile/fd_tile_private.h" +#include #include #include #include @@ -23,313 +27,7 @@ #include #include -FD_IMPORT_CSTR( default_config, "src/app/fdctl/config/default.toml" ); - -static char * -default_user( void ) { - char * name = getenv( "SUDO_USER" ); - if( FD_UNLIKELY( name ) ) return name; - - name = getenv( "LOGNAME" ); - if( FD_LIKELY( name ) ) return name; - - name = getlogin(); - if( FD_UNLIKELY( !name ) ) FD_LOG_ERR(( "getlogin failed (%i-%s)", errno, fd_io_strerror( errno ) )); - return name; -} - -static int parse_key_value( config_t * config, - const char * section, - const char * key, - char * value ) { -#define ENTRY_STR(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - ulong len = strlen( value ); \ - if( FD_UNLIKELY( len < 2 || value[ 0 ] != '"' || value[ len - 1 ] != '"' ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - if( FD_UNLIKELY( len >= sizeof( config->esection edot ekey ) + 2 ) ) \ - FD_LOG_ERR(( "value for %s.%s is too long: `%s`", section, key, value )); \ - strncpy( config->esection edot ekey, value + 1, len - 2 ); \ - config->esection edot ekey[ len - 2 ] = '\0'; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_VSTR(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - ulong len = strlen( value ); \ - if( FD_UNLIKELY( len < 2 || value[ 0 ] != '"' || value[ len - 1 ] != '"' ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - if( FD_UNLIKELY( len >= sizeof( config->esection edot ekey[ 0 ] ) + 2 ) ) \ - FD_LOG_ERR(( "value for %s.%s is too long: `%s`", section, key, value )); \ - if( FD_UNLIKELY( config->esection edot ekey##_cnt >= sizeof( config->esection edot ekey) ) ) \ - FD_LOG_ERR(( "too many values for %s.%s: `%s`", section, key, value )); \ - strncpy( config->esection edot ekey[ config->esection edot ekey##_cnt ], value + 1, len - 2 ); \ - config->esection edot ekey[ config->esection edot ekey##_cnt ][ len - 2 ] = '\0'; \ - config->esection edot ekey##_cnt++; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_UINT(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - if( FD_UNLIKELY( strlen( value ) < 1 ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - char * src = value; \ - char * dst = value; \ - while( *src ) { \ - if( *src != '_' ) *dst++ = *src; \ - src++; \ - } \ - *dst = '\0'; \ - char * endptr; \ - ulong result = strtoul( value, &endptr, 10 ); \ - if( FD_UNLIKELY( *endptr != '\0' || result > UINT_MAX ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - config->esection edot ekey = (uint)result; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_ULONG(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - if( FD_UNLIKELY( strlen( value ) < 1 ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - char * src = value; \ - char * dst = value; \ - while( *src ) { \ - if( *src != '_' ) *dst++ = *src; \ - src++; \ - } \ - *dst = '\0'; \ - char * endptr; \ - ulong result = strtoul( value, &endptr, 10 ); \ - if( FD_UNLIKELY( *endptr!='\0' ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - config->esection edot ekey = result; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_VUINT(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - if( FD_UNLIKELY( strlen( value ) < 1 ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - char * src = value; \ - char * dst = value; \ - while( *src ) { \ - if( *src != '_' ) *dst++ = *src; \ - src++; \ - } \ - *dst = '\0'; \ - char * endptr; \ - unsigned long int result = strtoul( value, &endptr, 10 ); \ - if( FD_UNLIKELY( *endptr != '\0' || result > UINT_MAX ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - config->esection edot ekey[ config->esection edot ekey##_cnt ] = (uint)result; \ - config->esection edot ekey##_cnt++; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_USHORT(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - if( FD_UNLIKELY( strlen( value ) < 1 ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - char * endptr; \ - unsigned long int result = strtoul( value, &endptr, 10 ); \ - if( FD_UNLIKELY( *endptr != '\0' || result > USHORT_MAX ) ) { \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - config->esection edot ekey = (ushort)result; \ - return 1; \ - } \ - } while( 0 ) - -#define ENTRY_BOOL(edot, esection, ekey) do { \ - if( FD_UNLIKELY( !strcmp( section, #esection ) && !strcmp( key, #ekey ) ) ) { \ - if( FD_LIKELY( !strcmp( value, "true" ) ) ) \ - config->esection edot ekey = 1; \ - else if( FD_LIKELY( !strcmp( value, "false" ) ) ) \ - config->esection edot ekey = 0; \ - else \ - FD_LOG_ERR(( "invalid value for %s.%s: `%s`", section, key, value )); \ - return 1; \ - } \ - } while( 0 ) - - ENTRY_STR ( , , name ); - ENTRY_STR ( , , user ); - ENTRY_STR ( , , scratch_directory ); - ENTRY_STR ( , , dynamic_port_range ); - - ENTRY_STR ( ., log, path ); - ENTRY_STR ( ., log, colorize ); - ENTRY_STR ( ., log, level_logfile ); - ENTRY_STR ( ., log, level_stderr ); - ENTRY_STR ( ., log, level_flush ); - - ENTRY_STR ( ., ledger, path ); - ENTRY_STR ( ., ledger, accounts_path ); - ENTRY_UINT ( ., ledger, limit_size ); - ENTRY_VSTR ( ., ledger, account_indexes ); - ENTRY_VSTR ( ., ledger, account_index_exclude_keys ); - ENTRY_STR ( ., ledger, snapshot_archive_format ); - ENTRY_BOOL ( ., ledger, require_tower ); - - ENTRY_VSTR ( ., gossip, entrypoints ); - ENTRY_BOOL ( ., gossip, port_check ); - ENTRY_USHORT( ., gossip, port ); - ENTRY_STR ( ., gossip, host ); - - ENTRY_STR ( ., consensus, identity_path ); - ENTRY_STR ( ., consensus, vote_account_path ); - ENTRY_BOOL ( ., consensus, snapshot_fetch ); - ENTRY_BOOL ( ., consensus, genesis_fetch ); - ENTRY_BOOL ( ., consensus, poh_speed_test ); - ENTRY_STR ( ., consensus, expected_genesis_hash ); - ENTRY_UINT ( ., consensus, wait_for_supermajority_at_slot ); - ENTRY_STR ( ., consensus, expected_bank_hash ); - ENTRY_USHORT( ., consensus, expected_shred_version ); - ENTRY_BOOL ( ., consensus, wait_for_vote_to_start_leader ); - ENTRY_VUINT ( ., consensus, hard_fork_at_slots ); - ENTRY_VSTR ( ., consensus, known_validators ); - ENTRY_BOOL ( ., consensus, os_network_limits_test ); - - ENTRY_USHORT( ., rpc, port ); - ENTRY_BOOL ( ., rpc, full_api ); - ENTRY_BOOL ( ., rpc, private ); - ENTRY_BOOL ( ., rpc, transaction_history ); - ENTRY_BOOL ( ., rpc, extended_tx_metadata_storage ); - ENTRY_BOOL ( ., rpc, only_known ); - ENTRY_BOOL ( ., rpc, pubsub_enable_block_subscription ); - ENTRY_BOOL ( ., rpc, pubsub_enable_vote_subscription ); - ENTRY_BOOL ( ., rpc, bigtable_ledger_storage ); - - ENTRY_BOOL ( ., snapshots, incremental_snapshots ); - ENTRY_UINT ( ., snapshots, full_snapshot_interval_slots ); - ENTRY_UINT ( ., snapshots, incremental_snapshot_interval_slots ); - ENTRY_STR ( ., snapshots, path ); - - ENTRY_STR ( ., layout, affinity ); - ENTRY_STR ( ., layout, solana_labs_affinity ); - ENTRY_UINT ( ., layout, net_tile_count ); - ENTRY_UINT ( ., layout, quic_tile_count ); - ENTRY_UINT ( ., layout, verify_tile_count ); - ENTRY_UINT ( ., layout, bank_tile_count ); - ENTRY_UINT ( ., layout, shred_tile_count ); - - ENTRY_STR ( ., hugetlbfs, mount_path ); - - ENTRY_STR ( ., tiles.net, interface ); - ENTRY_STR ( ., tiles.net, xdp_mode ); - ENTRY_UINT ( ., tiles.net, xdp_rx_queue_size ); - ENTRY_UINT ( ., tiles.net, xdp_tx_queue_size ); - ENTRY_UINT ( ., tiles.net, xdp_aio_depth ); - ENTRY_UINT ( ., tiles.net, send_buffer_size ); - - ENTRY_USHORT( ., tiles.quic, regular_transaction_listen_port ); - ENTRY_USHORT( ., tiles.quic, quic_transaction_listen_port ); - ENTRY_UINT ( ., tiles.quic, txn_reassembly_count ); - ENTRY_UINT ( ., tiles.quic, max_concurrent_connections ); - ENTRY_UINT ( ., tiles.quic, max_concurrent_streams_per_connection ); - ENTRY_UINT ( ., tiles.quic, stream_pool_cnt ); - ENTRY_UINT ( ., tiles.quic, max_concurrent_handshakes ); - ENTRY_UINT ( ., tiles.quic, max_inflight_quic_packets ); - ENTRY_UINT ( ., tiles.quic, tx_buf_size ); - ENTRY_UINT ( ., tiles.quic, idle_timeout_millis ); - ENTRY_BOOL ( ., tiles.quic, retry ); - - ENTRY_UINT ( ., tiles.verify, receive_buffer_size ); - ENTRY_UINT ( ., tiles.verify, mtu ); - - ENTRY_UINT ( ., tiles.dedup, signature_cache_size ); - - ENTRY_UINT ( ., tiles.pack, max_pending_transactions ); - - ENTRY_UINT ( ., tiles.shred, max_pending_shred_sets ); - ENTRY_USHORT( ., tiles.shred, shred_listen_port ); - - ENTRY_USHORT( ., tiles.metric, prometheus_listen_port ); - - ENTRY_BOOL ( ., development, sandbox ); - ENTRY_BOOL ( ., development, no_clone ); - ENTRY_BOOL ( ., development, no_solana_labs ); - ENTRY_BOOL ( ., development, bootstrap ); - ENTRY_STR ( ., development, topology ); - - ENTRY_BOOL ( ., development.netns, enabled ); - ENTRY_STR ( ., development.netns, interface0 ); - ENTRY_STR ( ., development.netns, interface0_mac ); - ENTRY_STR ( ., development.netns, interface0_addr ); - ENTRY_STR ( ., development.netns, interface1 ); - ENTRY_STR ( ., development.netns, interface1_mac ); - ENTRY_STR ( ., development.netns, interface1_addr ); - - ENTRY_BOOL ( ., development.gossip, allow_private_address ); - - ENTRY_UINT ( ., development.genesis, hashes_per_tick ); - ENTRY_UINT ( ., development.genesis, target_tick_duration_micros ); - ENTRY_UINT ( ., development.genesis, ticks_per_slot ); - ENTRY_UINT ( ., development.genesis, fund_initial_accounts ); - ENTRY_ULONG ( ., development.genesis, fund_initial_amount_lamports ); - ENTRY_ULONG ( ., development.genesis, vote_account_stake_lamports ); - ENTRY_BOOL ( ., development.genesis, warmup_epochs ); - - ENTRY_UINT ( ., development.bench, benchg_tile_count ); - ENTRY_UINT ( ., development.bench, benchs_tile_count ); - ENTRY_STR ( ., development.bench, affinity ); - ENTRY_BOOL ( ., development.bench, larger_max_cost_per_block ); - ENTRY_BOOL ( ., development.bench, larger_shred_limits_per_block ); - ENTRY_BOOL ( ., development.bench, rocksdb_disable_wal ); - - /* Firedancer-only configuration */ - - ENTRY_VSTR ( ., tiles.gossip, entrypoints ); - ENTRY_USHORT( ., tiles.gossip, gossip_listen_port ); - ENTRY_VUINT ( ., tiles.gossip, peer_ports ); - - ENTRY_BOOL ( ., consensus, vote ); - - ENTRY_USHORT( ., tiles.repair, repair_intake_listen_port ); - ENTRY_USHORT( ., tiles.repair, repair_serve_listen_port ); - - ENTRY_STR ( ., tiles.replay, blockstore_checkpt ); - ENTRY_STR ( ., tiles.replay, capture ); - ENTRY_ULONG ( ., tiles.replay, funk_rec_max ); - ENTRY_ULONG ( ., tiles.replay, funk_sz_gb ); - ENTRY_ULONG ( ., tiles.replay, funk_txn_max ); - ENTRY_STR ( ., tiles.replay, genesis ); - ENTRY_STR ( ., tiles.replay, incremental ); - ENTRY_STR ( ., tiles.replay, slots_replayed ); - ENTRY_STR ( ., tiles.replay, snapshot ); - ENTRY_ULONG ( ., tiles.replay, tpool_thread_count ); - - ENTRY_STR ( ., tiles.store_int, blockstore_restore ); - ENTRY_STR ( ., tiles.store_int, slots_pending ); - - /* We have encountered a token that is not recognized, return 0 to indicate failure. */ - return 0; -} +FD_IMPORT_BINARY( default_config, "src/app/fdctl/config/default.toml" ); void replace( char * in, @@ -356,139 +54,54 @@ replace( char * in, } static void -config_parse_array( const char * path, - config_t * config, - char * section, - char * key, - int * in_array, - char * value ) { - char * end = value + strlen( value ) - 1; - while( FD_UNLIKELY( *end == ' ' ) ) end--; - if( FD_LIKELY( *end == ']' ) ) { - *end = '\0'; - *in_array = 0; +fdctl_cfg_load_buf( config_t * out, + char const * buf, + ulong sz, + char const * path ) { + + static uchar pod_mem[ 1UL<<30 ]; + uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) ); + + uchar scratch[ 4096 ]; + long toml_err = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch) ); + if( FD_UNLIKELY( toml_err!=FD_TOML_SUCCESS ) ) { + FD_LOG_ERR(( "Invalid config (%s) at line %lu", path, toml_err )); } - char * saveptr; - char * token = strtok_r( value, ",", &saveptr ); - while( token ) { - while( FD_UNLIKELY( *token == ' ' ) ) token++; - char * end = token + strlen( token ) - 1; - while( FD_UNLIKELY( *end == ' ' ) ) end--; - *(end+1) = '\0'; - if( FD_LIKELY( end > token ) ) { - if( FD_UNLIKELY( !parse_key_value( config, section, key, token ) ) ) { - if( FD_UNLIKELY( path == NULL ) ) { - FD_LOG_ERR(( "Error while parsing the embedded configuration. The configuration had an unrecognized key [%s.%s].", section, key )); - } else { - FD_LOG_ERR(( "Error while parsing user configuration TOML file at %s. The configuration had an unrecognized key [%s.%s].", path, section, key )); - } - } - } - token = strtok_r( NULL, ",", &saveptr ); - } + fdctl_pod_to_cfg( out, pod ); + + fd_pod_delete( fd_pod_leave( pod ) ); } static void -config_parse_line( const char * path, - uint lineno, - char * line, - char * section, - int * in_array, - char * key, - config_t * out ) { - while( FD_LIKELY( *line == ' ' ) ) line++; - if( FD_UNLIKELY( *line == '#' || *line == '\0' || *line == '\n' ) ) return; - - if( FD_UNLIKELY( *in_array ) ) { - config_parse_array( path, out, section, key, in_array, line ); - return; - } +fdctl_cfg_load_file( config_t * out, + char const * path ) { - if( FD_UNLIKELY( *line == '[' ) ) { - char * end = strchr( line, ']' ); - if( FD_UNLIKELY( !end ) ) FD_LOG_ERR(( "invalid line %u: no closing bracket `%s`", lineno, line )); - if( FD_UNLIKELY( *(end+1) != '\0' ) ) FD_LOG_ERR(( "invalid line %u: no newline after closing bracket `%s`", lineno, line )); - *end = '\0'; - strcpy( section, line + 1 ); - return; + int fd = open( path, O_RDONLY ); + if( FD_UNLIKELY( fd<0 ) ) { + FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) )); } - char * equals = strchr( line, '=' ); - if( FD_UNLIKELY( !equals ) ) FD_LOG_ERR(( "invalid line %u: no equal character `%s`", lineno, line )); - - char * value = equals + 1; - while( FD_LIKELY( *value == ' ' ) ) value++; - while ( FD_UNLIKELY( equals > line && *(equals - 1) == ' ' ) ) equals--; - - *equals = '\0'; - strcpy( key, line ); + struct stat st; + if( FD_UNLIKELY( fstat( fd, &st ) ) ) { + FD_LOG_ERR(( "fstat(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) )); + } + ulong toml_sz = (ulong)st.st_size; - if( FD_UNLIKELY( *value == '[' ) ) { - *in_array = 1; - value++; - config_parse_array( path, out, section, key, in_array, value ); - } else { - if( FD_UNLIKELY( !parse_key_value( out, section, key, value ) ) ) { - if( FD_UNLIKELY( path == NULL ) ) { - FD_LOG_ERR(( "Error while parsing the embedded configuration. The configuration had an unrecognized key [%s.%s].", section, key )); - } else { - FD_LOG_ERR(( "Error while parsing user configuration TOML file at %s. The configuration had an unrecognized key [%s.%s].", path, section, key )); - } - } + void * mem = mmap( NULL, toml_sz, PROT_READ, MAP_PRIVATE, fd, 0 ); + if( FD_UNLIKELY( mem==MAP_FAILED ) ) { + FD_LOG_ERR(( "mmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) )); } -} -static void -config_parse1( const char * config, - config_t * out ) { - char section[ 4096 ] = {0}; - char key[ 4096 ]; - uint lineno = 0; - int in_array = 0; - const char * line = config; - while( line ) { - lineno++; - char * next_line = strchr( line, '\n' ); - - ulong n = next_line ? (ulong)(next_line - line) : strlen( line ); - if( n >= 4096 ) FD_LOG_ERR(( "line %u too long `%s`", lineno, line )); - - char line_copy[ 4096 ]; - strncpy( line_copy, line, sizeof( line_copy ) - 1 ); // -1 to silence linter - line_copy[ n ] = '\0'; - - config_parse_line( NULL , lineno, line_copy, section, &in_array, key, out ); - - if( FD_LIKELY( next_line ) ) next_line++; - line = next_line; + if( FD_UNLIKELY( 0!=close( fd ) ) ) { + FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) )); } -} -static void -config_parse_file( const char * path, - config_t * out ) { - FILE * fp = fopen( path, "r" ); - if( FD_UNLIKELY( !fp ) ) FD_LOG_ERR(( "could not open configuration file `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) )); - - uint lineno = 0; - char line[ 4096 ]; - char key[ 4096 ]; - int in_array = 0; - char section[ 4096 ] = {0}; - while( FD_LIKELY( fgets( line, 4096, fp ) ) ) { - lineno++; - ulong len = strlen( line ); - if( FD_UNLIKELY( len==4095UL ) ) FD_LOG_ERR(( "line %u too long in `%s`", lineno, path )); - if( FD_LIKELY( len ) ) { - line[ len-1UL ] = '\0'; /* chop off newline */ - config_parse_line( path, lineno, line, section, &in_array, key, out ); - } + fdctl_cfg_load_buf( out, mem, toml_sz, path ); + + if( FD_UNLIKELY( 0!=munmap( mem, toml_sz ) ) ) { + FD_LOG_ERR(( "munmap(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) )); } - if( FD_UNLIKELY( ferror( fp ) ) ) - FD_LOG_ERR(( "error reading `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) )); - if( FD_LIKELY( fclose( fp ) ) ) - FD_LOG_ERR(( "error closing `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) )); } static uint @@ -622,7 +235,7 @@ fdctl_obj_footprint( fd_topo_t const * topo, ulong __x = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, name ); \ if( FD_UNLIKELY( __x==ULONG_MAX ) ) FD_LOG_ERR(( "obj.%lu.%s was not set", obj->id, name )); \ __x; })) - + ulong sz = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.%s", obj->id, "sz" ); if( sz!=ULONG_MAX ) { return sz; @@ -822,11 +435,24 @@ cluster_to_cstr( ulong cluster ) { } } +static char * +default_user( void ) { + char * name = getenv( "SUDO_USER" ); + if( FD_UNLIKELY( name ) ) return name; + + name = getenv( "LOGNAME" ); + if( FD_LIKELY( name ) ) return name; + + name = getlogin(); + if( FD_UNLIKELY( !name ) ) FD_LOG_ERR(( "getlogin failed (%i-%s)", errno, fd_io_strerror( errno ) )); + return name; +} + void -config_parse( int * pargc, - char *** pargv, - config_t * config ) { - config_parse1( default_config, config ); +fdctl_cfg_from_env( int * pargc, + char *** pargv, + config_t * config ) { + fdctl_cfg_load_buf( config, (char const *)default_config, default_config_sz, "default" ); const char * user_config = fd_env_strip_cmdline_cstr( pargc, @@ -836,7 +462,7 @@ config_parse( int * pargc, NULL ); if( FD_LIKELY( user_config ) ) { - config_parse_file( user_config, config ); + fdctl_cfg_load_file( config, user_config ); } int netns = fd_env_strip_cmdline_contains( pargc, pargv, "--netns" ); @@ -1045,7 +671,7 @@ config_parse( int * pargc, } int -config_write_memfd( config_t * config ) { +fdctl_cfg_to_memfd( config_t * config ) { int config_memfd = memfd_create( "fd_config", 0 ); if( FD_UNLIKELY( -1==config_memfd ) ) FD_LOG_ERR(( "memfd_create() failed (%i-%s)", errno, fd_io_strerror( errno ) )); if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "ftruncate() failed (%i-%s)", errno, fd_io_strerror( errno ) )); diff --git a/src/app/fdctl/config.h b/src/app/fdctl/config.h index 1b59df1970..48b2c487db 100644 --- a/src/app/fdctl/config.h +++ b/src/app/fdctl/config.h @@ -227,13 +227,13 @@ typedef struct { } metric; /* Firedancer-only tile configs */ - + struct { ulong entrypoints_cnt; char entrypoints[16][256]; ushort gossip_listen_port; ulong peer_ports_cnt; - uint peer_ports[16]; + ushort peer_ports[16]; } gossip; struct { @@ -271,22 +271,24 @@ typedef struct { ulong memlock_max_bytes( config_t * const config ); -/* config_parse() loads a full configuration object from the provided +/* fdctl_cfg_from_env() loads a full configuration object from the provided arguments or the environment. First, the `default.toml` file is loaded as a base, and then if a FIREDANCER_CONFIG_FILE environment variable is provided, or a --config command line argument, the `toml` file at that path is loaded and applied on top of the default configuration. This exits the program if it encounters any issue while loading or parsing the configuration. */ + void -config_parse( int * pargc, - char *** pargv, - config_t * config ); +fdctl_cfg_from_env( int * pargc, + char *** pargv, + config_t * config ); /* Create a memfd and write the contents of the config struct into it. Used when execve() a child process so that it can read back in the same config as we did. */ + int -config_write_memfd( config_t * config ); +fdctl_cfg_to_memfd( config_t * config ); #endif /* HEADER_fd_src_app_fdctl_config_h */ diff --git a/src/app/fdctl/config_parse.c b/src/app/fdctl/config_parse.c new file mode 100644 index 0000000000..d587a50b87 --- /dev/null +++ b/src/app/fdctl/config_parse.c @@ -0,0 +1,362 @@ +#include "config_parse.h" + +/* Pod query utils ****************************************************/ + +static int +fdctl_cfg_get_cstr_( char * out, + ulong out_sz, + fd_pod_info_t const * info, + char const * path ) { + if( FD_UNLIKELY( info->val_type != FD_POD_VAL_TYPE_CSTR ) ) { + FD_LOG_WARNING(( "invalid value for `%s`", path )); + return 0; + } + char const * str = info->val; + ulong sz = strlen( str ) + 1; + if( FD_UNLIKELY( sz > out_sz ) ) { + FD_LOG_WARNING(( "`%s`: too long (max %ld)", path, (long)out_sz-1L )); + return 0; + } + fd_memcpy( out, str, sz ); + return 1; +} + +#define fdctl_cfg_get_cstr( out, out_sz, info, path ) \ + fdctl_cfg_get_cstr_( *out, out_sz, info, path ) + +static int +fdctl_cfg_get_ulong( ulong * out, + ulong out_sz FD_PARAM_UNUSED, + fd_pod_info_t const * info, + char const * path ) { + + ulong num; + switch( info->val_type ) { + case FD_POD_VAL_TYPE_LONG: + fd_ulong_svw_dec( (uchar const *)info->val, &num ); + long snum = fd_long_zz_dec( num ); + if( snum < 0L ) { + FD_LOG_WARNING(( "`%s` cannot be negative", path )); + return 0; + } + num = (ulong)snum; + break; + case FD_POD_VAL_TYPE_ULONG: + fd_ulong_svw_dec( (uchar const *)info->val, &num ); + break; + default: + FD_LOG_WARNING(( "invalid value for `%s`", path )); + return 0; + } + + *out = num; + return 1; +} + +static int +fdctl_cfg_get_uint( uint * out, + ulong out_sz FD_PARAM_UNUSED, + fd_pod_info_t const * info, + char const * path ) { + ulong num; + if( FD_UNLIKELY( !fdctl_cfg_get_ulong( &num, sizeof(num), info, path ) ) ) return 0; + if( num > UINT_MAX ) { + FD_LOG_WARNING(( "`%s` is out of bounds (%lx)", path, num )); + return 0; + } + *out = (uint)num; + return 1; +} + +static int +fdctl_cfg_get_ushort( ushort * out, + ulong out_sz FD_PARAM_UNUSED, + fd_pod_info_t const * info, + char const * path ) { + ulong num; + if( FD_UNLIKELY( !fdctl_cfg_get_ulong( &num, sizeof(num), info, path ) ) ) return 0; + if( num > USHORT_MAX ) { + FD_LOG_WARNING(( "`%s` is out of bounds (%lx)", path, num )); + return 0; + } + *out = (ushort)num; + return 1; +} + +static int +fdctl_cfg_get_bool( int * out, + ulong out_sz FD_PARAM_UNUSED, + fd_pod_info_t const * info, + char const * path ) { + if( FD_UNLIKELY( info->val_type != FD_POD_VAL_TYPE_INT ) ) { + FD_LOG_WARNING(( "invalid value for `%s`", path )); + return 0; + } + ulong u; fd_ulong_svw_dec( (uchar const *)info->val, &u ); + *out = (int)u; + return 1; +} + +/* Find leftover ******************************************************/ + +/* fdctl_pod_find_leftover recursively searches for non-subpod keys in + pod. Prints to the warning log if it finds any. Used to detect + config keys that were not recognized by fdctl. Returns 0 if no + leftover key was found. Otherwise, returns a non-zero number of + segments of the leftover key. The key can be reassembled by joining + stack[0] .. stack[depth-1]. + + Not thread safe (uses global buffer). */ + +# define FDCTL_CFG_MAX_DEPTH (16) + +static ulong +fdctl_pod_find_leftover_recurse( uchar * pod, + char const ** stack, + ulong depth ) { + + if( FD_UNLIKELY( depth+1 >= FDCTL_CFG_MAX_DEPTH ) ) { + FD_LOG_WARNING(( "configuration file has too many nested keys" )); + return depth; + } + + for( fd_pod_iter_t iter = fd_pod_iter_init( pod ); !fd_pod_iter_done( iter ); iter = fd_pod_iter_next( iter ) ) { + fd_pod_info_t info = fd_pod_iter_info( iter ); + stack[ depth ] = info.key; + depth++; + if( FD_LIKELY( info.val_type == FD_POD_VAL_TYPE_SUBPOD ) ) { + ulong sub_depth = fdctl_pod_find_leftover_recurse( (uchar *)info.val, stack, depth ); + if( FD_UNLIKELY( sub_depth ) ) return sub_depth; + } else { + return depth; + } + depth--; + } + + return 0; +} + +static int +fdctl_pod_find_leftover( uchar * pod ) { + + static char const * stack[ FDCTL_CFG_MAX_DEPTH ]; + ulong depth = fdctl_pod_find_leftover_recurse( pod, stack, 0UL ); + if( FD_LIKELY( !depth ) ) return 0; + + static char path[ 64*FDCTL_CFG_MAX_DEPTH + 4 ]; + char * c = fd_cstr_init( path ); + char * end = path + 64*FDCTL_CFG_MAX_DEPTH - 1; + for( ulong j=0UL; j= end ) { + c = fd_cstr_append_text( c, "...", 3UL ); + break; + } + c = fd_cstr_append_text( c, key, key_len ); + c = fd_cstr_append_char( c, '.' ); + } + c -= 1; + fd_cstr_fini( c ); + + FD_LOG_WARNING(( "Unrecognized key `%s`", path )); + return 1; +} + +/* Converter **********************************************************/ + +config_t * +fdctl_pod_to_cfg( config_t * config, + uchar * pod ) { + +# define CFG_POP( type, edot, esection, ekey ) \ + do { \ + char const * key = #esection #edot #ekey; \ + fd_pod_info_t info[1]; \ + if( fd_pod_query( pod, key, info ) ) break; \ + if( FD_UNLIKELY( !fdctl_cfg_get_##type( \ + &config->esection edot ekey,sizeof(config->esection edot ekey),\ + info, key ) ) ) \ + return NULL; \ + fd_pod_remove( pod, key ); \ + } while(0) + +# define CFG_POP_ARRAY( type, edot, esection, ekey ) \ + do { \ + char const * key = #esection #edot #ekey; \ + fd_pod_info_t info[1]; \ + if( fd_pod_query( pod, key, info ) ) break; \ + if( FD_UNLIKELY( info->val_type!=FD_POD_VAL_TYPE_SUBPOD ) ) { \ + FD_LOG_WARNING(( "`%s`: expected array", key )); \ + return NULL; \ + } \ + ulong arr_len = sizeof( config->esection edot ekey ) / sizeof( config->esection edot ekey[ 0 ] ); \ + ulong j = 0UL; \ + for( fd_pod_iter_t iter = fd_pod_iter_init( info->val ); !fd_pod_iter_done( iter ); iter = fd_pod_iter_next( iter ) ) { \ + if( FD_UNLIKELY( j>=arr_len ) ) { \ + FD_LOG_WARNING(( "`%s`: too many values (max %lu)", key, arr_len )); \ + return NULL; \ + } \ + fdctl_cfg_get_##type( &config->esection edot ekey[j], sizeof(config->esection edot ekey[j]), info, key ); \ + j++; \ + } \ + config->esection edot ekey ## _cnt = j; \ + fd_pod_remove( pod, key ); \ + } while(0) + + CFG_POP ( cstr, , , name ); + CFG_POP ( cstr, , , user ); + CFG_POP ( cstr, , , scratch_directory ); + CFG_POP ( cstr, , , dynamic_port_range ); + + CFG_POP ( cstr, ., log, path ); + CFG_POP ( cstr, ., log, colorize ); + CFG_POP ( cstr, ., log, level_logfile ); + CFG_POP ( cstr, ., log, level_stderr ); + CFG_POP ( cstr, ., log, level_flush ); + + CFG_POP ( cstr, ., ledger, path ); + CFG_POP ( cstr, ., ledger, accounts_path ); + CFG_POP ( uint, ., ledger, limit_size ); + CFG_POP_ARRAY( cstr, ., ledger, account_indexes ); + CFG_POP_ARRAY( cstr, ., ledger, account_index_exclude_keys ); + CFG_POP ( bool, ., ledger, require_tower ); + CFG_POP ( cstr, ., ledger, snapshot_archive_format ); + + CFG_POP_ARRAY( cstr, ., gossip, entrypoints ); + CFG_POP ( bool, ., gossip, port_check ); + CFG_POP ( ushort, ., gossip, port ); + CFG_POP ( cstr, ., gossip, host ); + + CFG_POP ( cstr, ., consensus, identity_path ); + CFG_POP ( cstr, ., consensus, vote_account_path ); + CFG_POP ( bool, ., consensus, snapshot_fetch ); + CFG_POP ( bool, ., consensus, genesis_fetch ); + CFG_POP ( bool, ., consensus, poh_speed_test ); + CFG_POP ( cstr, ., consensus, expected_genesis_hash ); + CFG_POP ( uint, ., consensus, wait_for_supermajority_at_slot ); + CFG_POP ( cstr, ., consensus, expected_bank_hash ); + CFG_POP ( ushort, ., consensus, expected_shred_version ); + CFG_POP ( bool, ., consensus, wait_for_vote_to_start_leader ); + CFG_POP_ARRAY( uint, ., consensus, hard_fork_at_slots ); + CFG_POP_ARRAY( cstr, ., consensus, known_validators ); + CFG_POP ( bool, ., consensus, os_network_limits_test ); + + CFG_POP ( ushort, ., rpc, port ); + CFG_POP ( bool, ., rpc, full_api ); + CFG_POP ( bool, ., rpc, private ); + CFG_POP ( bool, ., rpc, transaction_history ); + CFG_POP ( bool, ., rpc, extended_tx_metadata_storage ); + CFG_POP ( bool, ., rpc, only_known ); + CFG_POP ( bool, ., rpc, pubsub_enable_block_subscription ); + CFG_POP ( bool, ., rpc, pubsub_enable_vote_subscription ); + CFG_POP ( bool, ., rpc, bigtable_ledger_storage ); + + CFG_POP ( bool, ., snapshots, incremental_snapshots ); + CFG_POP ( uint, ., snapshots, full_snapshot_interval_slots ); + CFG_POP ( uint, ., snapshots, incremental_snapshot_interval_slots ); + CFG_POP ( cstr, ., snapshots, path ); + + CFG_POP ( cstr, ., layout, affinity ); + CFG_POP ( cstr, ., layout, solana_labs_affinity ); + CFG_POP ( uint, ., layout, net_tile_count ); + CFG_POP ( uint, ., layout, quic_tile_count ); + CFG_POP ( uint, ., layout, verify_tile_count ); + CFG_POP ( uint, ., layout, bank_tile_count ); + CFG_POP ( uint, ., layout, shred_tile_count ); + + CFG_POP ( cstr, ., hugetlbfs, mount_path ); + + CFG_POP ( cstr, ., tiles.net, interface ); + CFG_POP ( cstr, ., tiles.net, xdp_mode ); + CFG_POP ( uint, ., tiles.net, xdp_rx_queue_size ); + CFG_POP ( uint, ., tiles.net, xdp_tx_queue_size ); + CFG_POP ( uint, ., tiles.net, xdp_aio_depth ); + CFG_POP ( uint, ., tiles.net, send_buffer_size ); + + CFG_POP ( ushort, ., tiles.quic, regular_transaction_listen_port ); + CFG_POP ( ushort, ., tiles.quic, quic_transaction_listen_port ); + CFG_POP ( uint, ., tiles.quic, txn_reassembly_count ); + CFG_POP ( uint, ., tiles.quic, max_concurrent_connections ); + CFG_POP ( uint, ., tiles.quic, max_concurrent_streams_per_connection ); + CFG_POP ( uint, ., tiles.quic, stream_pool_cnt ); + CFG_POP ( uint, ., tiles.quic, max_concurrent_handshakes ); + CFG_POP ( uint, ., tiles.quic, max_inflight_quic_packets ); + CFG_POP ( uint, ., tiles.quic, tx_buf_size ); + CFG_POP ( uint, ., tiles.quic, idle_timeout_millis ); + CFG_POP ( bool, ., tiles.quic, retry ); + + CFG_POP ( uint, ., tiles.verify, receive_buffer_size ); + CFG_POP ( uint, ., tiles.verify, mtu ); + + CFG_POP ( uint, ., tiles.dedup, signature_cache_size ); + + CFG_POP ( uint, ., tiles.pack, max_pending_transactions ); + + CFG_POP ( uint, ., tiles.shred, max_pending_shred_sets ); + CFG_POP ( ushort, ., tiles.shred, shred_listen_port ); + + CFG_POP ( ushort, ., tiles.metric, prometheus_listen_port ); + + CFG_POP ( bool, ., development, sandbox ); + CFG_POP ( bool, ., development, no_clone ); + CFG_POP ( bool, ., development, no_solana_labs ); + CFG_POP ( bool, ., development, bootstrap ); + CFG_POP ( cstr, ., development, topology ); + + CFG_POP ( bool, ., development.netns, enabled ); + CFG_POP ( cstr, ., development.netns, interface0 ); + CFG_POP ( cstr, ., development.netns, interface0_mac ); + CFG_POP ( cstr, ., development.netns, interface0_addr ); + CFG_POP ( cstr, ., development.netns, interface1 ); + CFG_POP ( cstr, ., development.netns, interface1_mac ); + CFG_POP ( cstr, ., development.netns, interface1_addr ); + + CFG_POP ( bool, ., development.gossip, allow_private_address ); + + CFG_POP ( ulong, ., development.genesis, hashes_per_tick ); + CFG_POP ( ulong, ., development.genesis, target_tick_duration_micros ); + CFG_POP ( ulong, ., development.genesis, ticks_per_slot ); + CFG_POP ( ulong, ., development.genesis, fund_initial_accounts ); + CFG_POP ( ulong, ., development.genesis, fund_initial_amount_lamports ); + CFG_POP ( ulong, ., development.genesis, vote_account_stake_lamports ); + CFG_POP ( bool, ., development.genesis, warmup_epochs ); + + CFG_POP ( uint, ., development.bench, benchg_tile_count ); + CFG_POP ( uint, ., development.bench, benchs_tile_count ); + CFG_POP ( cstr, ., development.bench, affinity ); + CFG_POP ( bool, ., development.bench, larger_max_cost_per_block ); + CFG_POP ( bool, ., development.bench, larger_shred_limits_per_block ); + CFG_POP ( bool, ., development.bench, rocksdb_disable_wal ); + + /* Firedancer-only configuration */ + + CFG_POP_ARRAY( cstr, ., tiles.gossip, entrypoints ); + CFG_POP ( ushort, ., tiles.gossip, gossip_listen_port ); + CFG_POP_ARRAY( ushort, ., tiles.gossip, peer_ports ); + + CFG_POP ( bool, ., consensus, vote ); + + CFG_POP ( ushort, ., tiles.repair, repair_intake_listen_port ); + CFG_POP ( ushort, ., tiles.repair, repair_serve_listen_port ); + + CFG_POP ( cstr, ., tiles.replay, blockstore_checkpt ); + CFG_POP ( cstr, ., tiles.replay, capture ); + CFG_POP ( ulong, ., tiles.replay, funk_rec_max ); + CFG_POP ( ulong, ., tiles.replay, funk_sz_gb ); + CFG_POP ( ulong, ., tiles.replay, funk_txn_max ); + CFG_POP ( cstr, ., tiles.replay, genesis ); + CFG_POP ( cstr, ., tiles.replay, incremental ); + CFG_POP ( cstr, ., tiles.replay, slots_replayed ); + CFG_POP ( cstr, ., tiles.replay, snapshot ); + CFG_POP ( ulong, ., tiles.replay, tpool_thread_count ); + + CFG_POP ( cstr, ., tiles.store_int, blockstore_restore ); + CFG_POP ( cstr, ., tiles.store_int, slots_pending ); + +# undef CFG_POP +# undef CFG_ARRAY + + if( FD_UNLIKELY( !fdctl_pod_find_leftover( pod ) ) ) return NULL; + return config; +} diff --git a/src/app/fdctl/config_parse.h b/src/app/fdctl/config_parse.h new file mode 100644 index 0000000000..6d0f342e97 --- /dev/null +++ b/src/app/fdctl/config_parse.h @@ -0,0 +1,20 @@ +#ifndef HEADER_fd_src_app_fdctl_config_parse_h +#define HEADER_fd_src_app_fdctl_config_parse_h + +#include "config.h" + +FD_PROTOTYPES_BEGIN + +/* fdctl_pod_to_cfg extracts configuration from pod to the typed config + struct. Any recognized keys are removed from pod. Logs errors to + warning log. Returns config on success, NULL on error. + + Not thread safe (uses global buffer). */ + +config_t * +fdctl_pod_to_cfg( config_t * config, + uchar * pod ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_app_fdctl_config_parse_h */ diff --git a/src/app/fdctl/fuzz_fdctl_config.c b/src/app/fdctl/fuzz_fdctl_config.c new file mode 100644 index 0000000000..e1ac017959 --- /dev/null +++ b/src/app/fdctl/fuzz_fdctl_config.c @@ -0,0 +1,33 @@ +#include "config_parse.h" +#include "../../ballet/toml/fd_toml.h" +#include "../../util/fd_util.h" +#include "../../util/sanitize/fd_fuzz.h" + +#include +#include + +int +LLVMFuzzerInitialize( int * argc, + char *** argv ) { + putenv( "FD_LOG_BACKTRACE=0" ); + fd_boot( argc, argv ); + atexit( fd_halt ); + fd_log_level_logfile_set( 4 ); + fd_log_level_stderr_set( 4 ); + return 0; +} + +int +LLVMFuzzerTestOneInput( uchar const * data, + ulong size ) { + + static uchar pod_mem[ 1UL<<16 ]; + uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) ); + + static uchar scratch[ 4096 ]; + (void)fd_toml_parse( data, size, pod, scratch, sizeof(scratch) ); + + static config_t config = {0}; + fdctl_pod_to_cfg( &config, pod ); + return 0; +} diff --git a/src/app/fdctl/main1.c b/src/app/fdctl/main1.c index a3f3885d6d..3ff007a6d3 100644 --- a/src/app/fdctl/main1.c +++ b/src/app/fdctl/main1.c @@ -162,7 +162,7 @@ fdctl_boot( int * pargc, can coordinate on metrics measurement. */ fd_tempo_set_tick_per_ns( config->tick_per_ns_mu, config->tick_per_ns_sigma ); } else { - config_parse( pargc, pargv, config ); + fdctl_cfg_from_env( pargc, pargv, config ); config->tick_per_ns_mu = fd_tempo_tick_per_ns( &config->tick_per_ns_sigma ); config->log.lock_fd = init_log_memfd(); config->log.log_fd = -1; diff --git a/src/app/fdctl/run/run.c b/src/app/fdctl/run/run.c index e3faf972cd..f875b109fc 100644 --- a/src/app/fdctl/run/run.c +++ b/src/app/fdctl/run/run.c @@ -204,7 +204,7 @@ main_pid_namespace( void * _args ) { char child_names[ FD_TOPO_MAX_TILES+1 ][ 32 ]; struct pollfd fds[ FD_TOPO_MAX_TILES+2 ]; - int config_memfd = config_write_memfd( config ); + int config_memfd = fdctl_cfg_to_memfd( config ); if( FD_UNLIKELY( config->development.debug_tile ) ) { fd_log_private_shared_lock[1] = 1; diff --git a/src/app/fddev/tests/test_fddev.c b/src/app/fddev/tests/test_fddev.c index c17bcb0b4d..f2f69d9aa0 100644 --- a/src/app/fddev/tests/test_fddev.c +++ b/src/app/fddev/tests/test_fddev.c @@ -145,7 +145,7 @@ fddev_test_run( int argc, fd_log_thread_set( "supervisor" ); static config_t config[1]; - config_parse( &argc, &argv, config ); + fdctl_cfg_from_env( &argc, &argv, config ); config->log.log_fd = fd_log_private_logfile_fd(); config->log.lock_fd = init_log_memfd(); config->tick_per_ns_mu = fd_tempo_tick_per_ns( &config->tick_per_ns_sigma );