Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_config.h"
3 : #include "fd_auto_net.h"
4 : #include "fd_config_private.h"
5 :
6 : #include "../platform/fd_net_util.h"
7 : #include "../platform/fd_sys_util.h"
8 : #include "../../util/tile/fd_tile_private.h"
9 : #include "../../ballet/toml/fd_toml.h"
10 : #include "../../disco/genesis/fd_genesis_cluster.h"
11 : #include "../../discof/genesis/fd_genesi_tile.h"
12 : #include "../../disco/net/fd_net_tile.h"
13 : #include "../../disco/pack/fd_pack_cost.h"
14 : #include "../../disco/pack/fd_microblock.h"
15 : #include "../../ballet/shred/fd_shred.h"
16 : #include "../../ballet/txn/fd_txn.h"
17 : #include "../../discof/restore/utils/fd_ssarchive.h"
18 : #include "../../discof/restore/utils/fd_wfs.h"
19 : #include "../../util/fd_boolau.h"
20 :
21 : #include <unistd.h>
22 : #include <fcntl.h>
23 : #include <errno.h>
24 : #include <stdlib.h> /* strtoul */
25 : #include <sys/utsname.h>
26 : #include <sys/mman.h>
27 :
28 : /* TODO: Rewrite this ... */
29 :
30 : static inline void
31 : replace( char * in,
32 : const char * pat,
33 0 : const char * sub ) {
34 0 : char * replace = strstr( in, pat );
35 0 : if( FD_LIKELY( replace ) ) {
36 0 : ulong pat_len = strlen( pat );
37 0 : ulong sub_len = strlen( sub );
38 0 : ulong in_len = strlen( in );
39 0 : if( FD_UNLIKELY( pat_len > in_len ) ) return;
40 :
41 0 : ulong total_len = in_len - pat_len + sub_len;
42 0 : if( FD_UNLIKELY( total_len >= PATH_MAX ) )
43 0 : FD_LOG_ERR(( "configuration scratch directory path too long: `%s`", in ));
44 :
45 0 : uchar after[PATH_MAX] = {0};
46 0 : fd_memcpy( after, replace + pat_len, strlen( replace + pat_len ) );
47 0 : fd_memcpy( replace, sub, sub_len );
48 0 : ulong after_len = strlen( ( const char * ) after );
49 0 : fd_memcpy( replace + sub_len, after, after_len );
50 0 : in[ total_len ] = '\0';
51 0 : }
52 0 : }
53 :
54 :
55 : FD_FN_CONST static inline int
56 0 : parse_log_level( char const * level ) {
57 0 : if( FD_UNLIKELY( !strcmp( level, "DEBUG" ) ) ) return 0;
58 0 : if( FD_UNLIKELY( !strcmp( level, "INFO" ) ) ) return 1;
59 0 : if( FD_UNLIKELY( !strcmp( level, "NOTICE" ) ) ) return 2;
60 0 : if( FD_UNLIKELY( !strcmp( level, "WARNING" ) ) ) return 3;
61 0 : if( FD_UNLIKELY( !strcmp( level, "ERR" ) ) ) return 4;
62 0 : if( FD_UNLIKELY( !strcmp( level, "CRIT" ) ) ) return 5;
63 0 : if( FD_UNLIKELY( !strcmp( level, "ALERT" ) ) ) return 6;
64 0 : if( FD_UNLIKELY( !strcmp( level, "EMERG" ) ) ) return 7;
65 0 : return -1;
66 0 : }
67 :
68 : FD_FN_CONST static inline int
69 0 : parse_core_dump_level( char const * level ) {
70 0 : if( FD_UNLIKELY( !strcmp( level, "disabled" ) ) ) return FD_TOPO_CORE_DUMP_LEVEL_DISABLED;
71 0 : if( FD_UNLIKELY( !strcmp( level, "minimal" ) ) ) return FD_TOPO_CORE_DUMP_LEVEL_MINIMAL;
72 0 : if( FD_UNLIKELY( !strcmp( level, "regular" ) ) ) return FD_TOPO_CORE_DUMP_LEVEL_REGULAR;
73 0 : if( FD_UNLIKELY( !strcmp( level, "full" ) ) ) return FD_TOPO_CORE_DUMP_LEVEL_FULL;
74 0 : return -1;
75 0 : }
76 :
77 : void
78 : fd_config_load_buf( fd_config_t * out,
79 : char const * buf,
80 : ulong sz,
81 0 : char const * path ) {
82 0 : static uchar pod_mem[ 1UL<<26 ];
83 0 : uchar * pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) );
84 :
85 0 : fd_toml_err_info_t toml_err[1];
86 0 : uchar scratch[ 4096 ];
87 0 : int toml_errc = fd_toml_parse( buf, sz, pod, scratch, sizeof(scratch), toml_err );
88 0 : if( FD_UNLIKELY( toml_errc!=FD_TOML_SUCCESS ) ) {
89 0 : switch( toml_errc ) {
90 0 : case FD_TOML_ERR_POD:
91 0 : FD_LOG_ERR(( "Failed to parse config file (%s): ran out of buffer space while parsing", path ));
92 0 : break;
93 0 : case FD_TOML_ERR_SCRATCH:
94 0 : FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: ran out of scratch space while parsing", path, toml_err->line ));
95 0 : break;
96 0 : case FD_TOML_ERR_KEY:
97 0 : FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: oversize key", path, toml_err->line ));
98 0 : break;
99 0 : case FD_TOML_ERR_DUP:
100 0 : FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: duplicate key", path, toml_err->line ));
101 0 : break;
102 0 : case FD_TOML_ERR_RANGE:
103 0 : FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu: invalid value for key", path, toml_err->line ));
104 0 : break;
105 0 : case FD_TOML_ERR_PARSE:
106 0 : FD_LOG_ERR(( "Failed to parse config file (%s) at line %lu", path, toml_err->line ));
107 0 : break;
108 0 : default:
109 0 : FD_LOG_ERR(( "Failed to parse config file (%s): %s", path, fd_toml_strerror( toml_errc ) ));
110 0 : break;
111 0 : }
112 0 : }
113 :
114 0 : if( FD_UNLIKELY( !fd_config_extract_pod( pod, out ) ) ) FD_LOG_ERR(( "Failed to parse config file (%s): there are unrecognized keys logged above", path ));
115 :
116 0 : fd_pod_delete( fd_pod_leave( pod ) );
117 0 : }
118 :
119 : static void
120 0 : fd_config_fillf( fd_config_t * config ) {
121 0 : if( FD_UNLIKELY( strcmp( config->firedancer.layout.mode, "performance" ) && strcmp( config->firedancer.layout.mode, "efficient" ) ) )
122 0 : FD_LOG_ERR(( "[layout.mode] %s not recognized", config->firedancer.layout.mode ));
123 :
124 0 : if( FD_UNLIKELY( strcmp( config->paths.accounts, "" ) ) ) {
125 0 : replace( config->paths.accounts, "{user}", config->user );
126 0 : replace( config->paths.accounts, "{name}", config->name );
127 0 : } else {
128 0 : FD_TEST( fd_cstr_printf_check( config->paths.accounts, sizeof(config->paths.accounts), NULL, "%s/accounts.db", config->paths.base ) );
129 0 : }
130 :
131 0 : if( FD_UNLIKELY( strcmp( config->paths.stake_delegations, "" ) ) ) {
132 0 : replace( config->paths.stake_delegations, "{user}", config->user );
133 0 : replace( config->paths.stake_delegations, "{name}", config->name );
134 0 : } else {
135 0 : FD_TEST( fd_cstr_printf_check( config->paths.stake_delegations, sizeof(config->paths.stake_delegations), NULL, "%s/stakedelegations.db", config->paths.base ) );
136 0 : }
137 :
138 0 : if( FD_UNLIKELY( strcmp( config->paths.epoch_credits, "" ) ) ) {
139 0 : replace( config->paths.epoch_credits, "{user}", config->user );
140 0 : replace( config->paths.epoch_credits, "{name}", config->name );
141 0 : } else {
142 0 : FD_TEST( fd_cstr_printf_check( config->paths.epoch_credits, sizeof(config->paths.epoch_credits), NULL, "%s/epochcredits.db", config->paths.base ) );
143 0 : }
144 :
145 0 : if( FD_UNLIKELY( strcmp( config->paths.cost_tracker, "" ) ) ) {
146 0 : replace( config->paths.cost_tracker, "{user}", config->user );
147 0 : replace( config->paths.cost_tracker, "{name}", config->name );
148 0 : } else {
149 0 : FD_TEST( fd_cstr_printf_check( config->paths.cost_tracker, sizeof(config->paths.cost_tracker), NULL, "%s/costtracker.db", config->paths.base ) );
150 0 : }
151 :
152 0 : if( FD_UNLIKELY( strcmp( config->paths.vote_stakes, "" ) ) ) {
153 0 : replace( config->paths.vote_stakes, "{user}", config->user );
154 0 : replace( config->paths.vote_stakes, "{name}", config->name );
155 0 : } else {
156 0 : FD_TEST( fd_cstr_printf_check( config->paths.vote_stakes, sizeof(config->paths.vote_stakes), NULL, "%s/votestakes.db", config->paths.base ) );
157 0 : }
158 :
159 0 : if( FD_UNLIKELY( strcmp( config->paths.shredb, "" ) ) ) {
160 0 : replace( config->paths.shredb, "{user}", config->user );
161 0 : replace( config->paths.shredb, "{name}", config->name );
162 0 : } else {
163 0 : FD_TEST( fd_cstr_printf_check( config->paths.shredb, sizeof(config->paths.shredb), NULL, "%s/shreds.db", config->paths.base ) );
164 0 : }
165 :
166 0 : if( FD_UNLIKELY( strcmp( config->paths.guidb, "" ) ) ) {
167 0 : replace( config->paths.guidb, "{user}", config->user );
168 0 : replace( config->paths.guidb, "{name}", config->name );
169 0 : } else {
170 0 : FD_TEST( fd_cstr_printf_check( config->paths.guidb, sizeof(config->paths.guidb), NULL, "%s/gui.db", config->paths.base ) );
171 0 : }
172 :
173 0 : if( FD_UNLIKELY( strcmp( config->paths.vote_history, "" ) ) ) {
174 0 : replace( config->paths.vote_history, "{user}", config->user );
175 0 : replace( config->paths.vote_history, "{name}", config->name );
176 0 : } else {
177 0 : fd_cstr_ncpy( config->paths.vote_history, config->paths.base, sizeof(config->paths.vote_history) );
178 0 : }
179 :
180 0 : for( ulong i=0UL; i<config->firedancer.paths.authorized_voter_paths_cnt; i++ ) {
181 0 : replace( config->firedancer.paths.authorized_voter_paths[ i ], "{user}", config->user );
182 0 : replace( config->firedancer.paths.authorized_voter_paths[ i ], "{name}", config->name );
183 0 : }
184 0 : }
185 :
186 : static void
187 0 : fd_config_fill_net( fd_config_t * config ) {
188 0 : if( FD_UNLIKELY( !strcmp( config->net.interface, "" ) ) ) {
189 0 : uint ifindex;
190 0 : int result = fd_net_util_internet_ifindex( &ifindex );
191 0 : if( FD_UNLIKELY( -1==result && errno!=ENODEV ) ) FD_LOG_ERR(( "could not get network device index (%i-%s)", errno, fd_io_strerror( errno ) ));
192 0 : else if( FD_UNLIKELY( -1==result ) )
193 0 : FD_LOG_ERR(( "no network device found which routes to 8.8.8.8. If no network "
194 0 : "interface is specified in the configuration file, Firedancer "
195 0 : "tries to use the first network interface found which routes to "
196 0 : "8.8.8.8. You can see what this is by running `ip route get 8.8.8.8` "
197 0 : "You can fix this error by specifying a network interface to bind to in "
198 0 : "your configuration file under [net.interface]" ));
199 :
200 0 : if( FD_UNLIKELY( !if_indextoname( ifindex, config->net.interface ) ) )
201 0 : FD_LOG_ERR(( "could not get name of interface with index %u", ifindex ));
202 0 : }
203 :
204 0 : if( FD_UNLIKELY( !if_nametoindex( config->net.interface ) ) )
205 0 : FD_LOG_ERR(( "configuration specifies network interface `%s` which does not exist", config->net.interface ));
206 :
207 0 : char driver[ NAME_SZ ];
208 0 : fd_net_get_driver( driver, sizeof(driver), config->net.interface );
209 0 : if( !strcmp( config->net.provider, "mlx5" ) && FD_UNLIKELY( strcmp( driver, "mlx5_core" ) ) ) {
210 0 : FD_LOG_ERR(( "[net.provider] is set to \"mlx5\" but the network interface in use is not using the "
211 0 : "mlx5_core driver. Please ensure you are using a Mellanox ConnectX NIC of version 4 "
212 0 : "or newer. interface `%s` uses `%s`", config->net.interface, driver ));
213 0 : }
214 :
215 0 : uint iface_ip;
216 0 : if( FD_UNLIKELY( -1==fd_net_util_if_addr( config->net.interface, &iface_ip ) ) )
217 0 : FD_LOG_ERR(( "could not get IP address for interface `%s`", config->net.interface ));
218 :
219 0 : if( FD_UNLIKELY( strcmp( config->firedancer.gossip.host, "" ) ) ) {
220 0 : uint gossip_ip_addr = iface_ip;
221 0 : int has_gossip_ip4 = 0;
222 0 : if( FD_UNLIKELY( strlen( config->firedancer.gossip.host )<=15UL ) ) {
223 : /* Only sets gossip_ip_addr if it's a valid IPv4 address, otherwise assume it's a DNS name */
224 0 : has_gossip_ip4 = fd_cstr_to_ip4_addr( config->firedancer.gossip.host, &gossip_ip_addr );
225 0 : }
226 0 : if( FD_UNLIKELY( !fd_ip4_addr_is_public( gossip_ip_addr ) && config->is_live_cluster && has_gossip_ip4 ) )
227 0 : FD_LOG_ERR(( "Trying to use [gossip.host] " FD_IP4_ADDR_FMT " for listening to incoming "
228 0 : "transactions, but it is part of a private network and will not be routable "
229 0 : "for other Solana network nodes.", FD_IP4_ADDR_FMT_ARGS( gossip_ip_addr ) ));
230 0 : } else if( FD_UNLIKELY( !fd_ip4_addr_is_public( iface_ip ) && config->is_live_cluster ) ) {
231 0 : FD_LOG_ERR(( "Trying to use network interface `%s` for listening to incoming transactions, "
232 0 : "but it has IPv4 address " FD_IP4_ADDR_FMT " which is part of a private network "
233 0 : "and will not be routable for other Solana network nodes. If you are running "
234 0 : "behind a NAT and this interface is publicly reachable, you can continue by "
235 0 : "manually specifying the IP address to advertise in your configuration under "
236 0 : "[gossip.host].", config->net.interface, FD_IP4_ADDR_FMT_ARGS( iface_ip ) ));
237 0 : }
238 :
239 0 : config->net.ip_addr = iface_ip;
240 0 : }
241 :
242 : void
243 0 : fd_config_validate_consensus( fd_config_t const * config ) {
244 0 : int has_bank_hash = !!strcmp( config->firedancer.consensus.wait_for_supermajority_with_bank_hash, "" );
245 0 : int has_slot = !!config->firedancer.consensus.wait_for_supermajority_at_slot;
246 :
247 0 : if( FD_UNLIKELY( has_bank_hash && !has_slot ) ) {
248 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] is set but "
249 0 : "[consensus.wait_for_supermajority_at_slot] is 0 (disabled). "
250 0 : "Both must be configured together." ));
251 0 : }
252 0 : if( FD_UNLIKELY( has_slot && !has_bank_hash ) ) {
253 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_at_slot] is set but "
254 0 : "[consensus.wait_for_supermajority_with_bank_hash] is empty. "
255 0 : "Both must be configured together." ));
256 0 : }
257 : /* Makes string-empty and byte-zero hash_is_zero agree (fd_wfs.h).
258 : Canonical base58 makes this the only all-zeros encoding. */
259 0 : if( FD_UNLIKELY( has_bank_hash && !strcmp( config->firedancer.consensus.wait_for_supermajority_with_bank_hash,
260 0 : "11111111111111111111111111111111" ) ) ) {
261 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] decodes to all zeros, "
262 0 : "which is not a valid bank hash." ));
263 0 : }
264 0 : if( FD_UNLIKELY( has_bank_hash && !config->consensus.expected_shred_version ) ) {
265 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_with_bank_hash] requires "
266 0 : "[consensus.expected_shred_version] to be nonzero." ));
267 0 : }
268 0 : if( FD_UNLIKELY( config->firedancer.consensus.wait_for_supermajority_at_slot==ULONG_MAX ) ) {
269 0 : FD_LOG_ERR(( "[consensus.wait_for_supermajority_at_slot] is ULONG_MAX. "
270 0 : "No boot slot could ever match it." ));
271 0 : }
272 0 : int wfs_configured = fd_wfs_configured( config->firedancer.consensus.wait_for_supermajority_at_slot,
273 0 : !has_bank_hash,
274 0 : (ulong)config->consensus.expected_shred_version );
275 0 : if( FD_UNLIKELY( wfs_configured && config->consensus.wait_for_vote_to_start_leader==FD_BOOLAU_TRUE ) ) {
276 0 : FD_LOG_ERR(( "wait-for-supermajority is configured, which requires "
277 0 : "[consensus.wait_for_vote_to_start_leader] to be false or \"auto\"." ));
278 0 : }
279 0 : }
280 :
281 : void
282 : fd_config_fill( fd_config_t * config,
283 : int is_local_cluster,
284 0 : int dev ) {
285 0 : struct utsname utsname;
286 0 : if( FD_UNLIKELY( -1==uname( &utsname ) ) )
287 0 : FD_LOG_ERR(( "could not get uname (%i-%s)", errno, fd_io_strerror( errno ) ));
288 0 : fd_cstr_ncpy( config->hostname, utsname.nodename, sizeof(config->hostname) ); /* Just truncate the name if it's too long to fit */
289 :
290 0 : ulong cluster = fd_genesis_cluster_identify( config->consensus.expected_genesis_hash );
291 0 : config->is_live_cluster = cluster!=FD_CLUSTER_UNKNOWN;
292 0 : strcpy( config->cluster, fd_genesis_cluster_name( cluster ) );
293 :
294 0 : if( FD_UNLIKELY( !strcmp( config->user, "" ) ) ) {
295 0 : const char * user = fd_sys_util_login_user();
296 0 : 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." ));
297 0 : if( FD_UNLIKELY( strlen( user )>=sizeof( config->user ) ) ) FD_LOG_ERR(( "user name `%s` is too long", user ));
298 0 : strncpy( config->user, user, sizeof(config->user) );
299 0 : }
300 :
301 0 : if( FD_UNLIKELY( -1==fd_sys_util_user_to_uid( config->user, &config->uid, &config->gid ) ) ) FD_LOG_ERR(( "configuration file wants firedancer to run as user `%s` but it does not exist", config->user ));
302 0 : if( FD_UNLIKELY( !config->uid || !config->gid ) ) FD_LOG_ERR(( "firedancer cannot run as root. please specify a non-root user in the configuration file" ));
303 0 : if( FD_UNLIKELY( getuid()!=0U && config->uid!=getuid() ) ) FD_LOG_ERR(( "running as uid %u, but config specifies uid %u", getuid(), config->uid ));
304 0 : if( FD_UNLIKELY( getgid()!=0U && config->gid!=getgid() ) ) FD_LOG_ERR(( "running as gid %u, but config specifies gid %u", getgid(), config->gid ));
305 :
306 0 : FD_TEST( fd_cstr_printf_check( config->hugetlbfs.gigantic_page_mount_path,
307 0 : sizeof(config->hugetlbfs.gigantic_page_mount_path),
308 0 : NULL,
309 0 : "%s/.gigantic",
310 0 : config->hugetlbfs.mount_path ) );
311 0 : FD_TEST( fd_cstr_printf_check( config->hugetlbfs.huge_page_mount_path,
312 0 : sizeof(config->hugetlbfs.huge_page_mount_path),
313 0 : NULL,
314 0 : "%s/.huge",
315 0 : config->hugetlbfs.mount_path ) );
316 :
317 0 : ulong max_page_sz = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
318 0 : if( FD_UNLIKELY( max_page_sz!=FD_SHMEM_HUGE_PAGE_SZ && max_page_sz!=FD_SHMEM_GIGANTIC_PAGE_SZ ) ) FD_LOG_ERR(( "[hugetlbfs.max_page_size] must be \"huge\" or \"gigantic\"" ));
319 :
320 0 : replace( config->log.path, "{user}", config->user );
321 0 : replace( config->log.path, "{name}", config->name );
322 :
323 0 : if( FD_LIKELY( !strcmp( "auto", config->log.colorize ) ) ) config->log.colorize1 = 2;
324 0 : else if( FD_LIKELY( !strcmp( "true", config->log.colorize ) ) ) config->log.colorize1 = 1;
325 0 : else if( FD_LIKELY( !strcmp( "false", config->log.colorize ) ) ) config->log.colorize1 = 0;
326 0 : else FD_LOG_ERR(( "[log.colorize] must be one of \"auto\", \"true\", or \"false\"" ));
327 :
328 0 : if( FD_LIKELY( 2==config->log.colorize1 ) ) {
329 0 : config->log.colorize1 = fd_log_should_colorize();
330 0 : }
331 :
332 0 : config->log.level_logfile1 = parse_log_level( config->log.level_logfile );
333 0 : config->log.level_stderr1 = parse_log_level( config->log.level_stderr );
334 0 : if( FD_UNLIKELY( !strcmp( config->log.level_flush, "NONE" ) ) ) config->log.level_flush1 = 8;
335 0 : else config->log.level_flush1 = parse_log_level( config->log.level_flush );
336 0 : if( FD_UNLIKELY( -1==config->log.level_logfile1 ) ) FD_LOG_ERR(( "unrecognized [log.level_logfile] `%s`", config->log.level_logfile ));
337 0 : if( FD_UNLIKELY( -1==config->log.level_stderr1 ) ) FD_LOG_ERR(( "unrecognized [log.level_stderr] `%s`", config->log.level_stderr ));
338 0 : if( FD_UNLIKELY( -1==config->log.level_flush1 ) ) FD_LOG_ERR(( "unrecognized [log.level_flush] `%s`", config->log.level_flush ));
339 :
340 0 : config->development.core_dump_level = parse_core_dump_level( config->development.core_dump );
341 0 : if( FD_UNLIKELY( -1==config->development.core_dump_level ) ) FD_LOG_ERR(( "unrecognized [development.core_dump] `%s`", config->development.core_dump ));
342 :
343 0 : replace( config->paths.base, "{user}", config->user );
344 0 : replace( config->paths.base, "{name}", config->name );
345 :
346 0 : if( FD_UNLIKELY( !strcmp( config->paths.identity_key, "" ) ) ) {
347 : /* Development binaries generate an identity key on boot. */
348 0 : if( FD_UNLIKELY( config->is_live_cluster && !dev ) ) FD_LOG_ERR(( "configuration file must specify [paths.identity_key] when joining a live cluster" ));
349 :
350 0 : FD_TEST( fd_cstr_printf_check( config->paths.identity_key,
351 0 : sizeof(config->paths.identity_key),
352 0 : NULL,
353 0 : "%s/identity.json",
354 0 : config->paths.base ) );
355 0 : } else {
356 0 : replace( config->paths.identity_key, "{user}", config->user );
357 0 : replace( config->paths.identity_key, "{name}", config->name );
358 0 : }
359 :
360 0 : replace( config->paths.vote_account, "{user}", config->user );
361 0 : replace( config->paths.vote_account, "{name}", config->name );
362 :
363 0 : if( FD_UNLIKELY( strcmp( config->paths.snapshots, "" ) ) ) {
364 0 : replace( config->paths.snapshots, "{user}", config->user );
365 0 : replace( config->paths.snapshots, "{name}", config->name );
366 0 : } else {
367 0 : FD_TEST( fd_cstr_printf_check( config->paths.snapshots, sizeof(config->paths.snapshots), NULL, "%s/snapshots", config->paths.base ) );
368 0 : }
369 :
370 0 : if( FD_UNLIKELY( strcmp( config->paths.genesis, "" ) ) ) {
371 0 : replace( config->paths.genesis, "{user}", config->user );
372 0 : replace( config->paths.genesis, "{name}", config->name );
373 0 : } else {
374 0 : FD_TEST( fd_cstr_printf_check( config->paths.genesis, sizeof(config->paths.genesis), NULL, "%s/genesis.bin", config->paths.base ) );
375 0 : }
376 :
377 0 : long ts = -fd_log_wallclock();
378 0 : config->is_dev = dev;
379 :
380 0 : config->tick_per_ns_mu = dev ? fd_tempo_tick_per_ns_dev( &config->tick_per_ns_sigma )
381 0 : : fd_tempo_tick_per_ns ( &config->tick_per_ns_sigma );
382 0 : FD_LOG_INFO(( "calibrating fd_tempo tick_per_ns took %ld ms", (fd_log_wallclock()+ts)/(1000L*1000L) ));
383 :
384 0 : if( 0!=strcmp( config->net.bind_address, "" ) ) {
385 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->net.bind_address, &config->net.bind_address_parsed ) ) ) {
386 0 : FD_LOG_ERR(( "`net.bind_address` is not a valid IPv4 address" ));
387 0 : }
388 0 : }
389 :
390 0 : if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "perf" ) ) ) config->tiles.pack.schedule_strategy_enum = 0;
391 0 : else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "balanced" ) ) ) config->tiles.pack.schedule_strategy_enum = 1;
392 0 : else if( FD_LIKELY( !strcmp( config->tiles.pack.schedule_strategy, "revenue" ) ) ) {
393 0 : FD_LOG_ERR(( "the revenue scheduler has been removed. Please update [tiles.pack.schedule_strategy]" ));
394 0 : }
395 0 : else FD_LOG_ERR(( "[tiles.pack.schedule_strategy] %s not recognized", config->tiles.pack.schedule_strategy ));
396 :
397 0 : fd_config_fill_net( config );
398 0 : fd_config_fillf( config );
399 :
400 0 : fd_auto_net( config );
401 0 : if( FD_UNLIKELY( !strcmp( config->net.provider, "auto" ) ) ) {
402 0 : FD_LOG_ERR(( "failed to resolve automatic network provider" ));
403 0 : }
404 0 : fd_config_validate( config );
405 :
406 0 : config->limits.max_cost_per_block = fd_ulong_if( !!config->development.bench.max_cost_per_block, config->development.bench.max_cost_per_block, FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND );
407 0 : config->limits.max_shreds_per_block = fd_ulong_if( !!config->development.bench.max_shreds_per_block, config->development.bench.max_shreds_per_block, FD_SHRED_BLK_MAX );
408 0 : config->limits.max_txn_per_slot = fd_ulong_min( config->limits.max_cost_per_block/FD_PACK_MIN_TXN_COST,
409 0 : (config->limits.max_shreds_per_block*FD_SHRED_DATA_PAYLOAD_MAX - 65UL*sizeof(fd_entry_batch_header_t))/FD_TXN_MIN_SERIALIZED_SZ ); /* 64 ticks + one giant microblock */
410 0 : if( FD_LIKELY( !config->development.bench.max_cost_per_block && !config->development.bench.max_shreds_per_block ) ) FD_TEST( config->limits.max_txn_per_slot==FD_MAX_TXN_PER_SLOT );
411 :
412 0 : if( FD_LIKELY( config->is_live_cluster) ) {
413 0 : if( FD_UNLIKELY( !config->development.sandbox ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration disables the sandbox which is a development only feature" ));
414 0 : if( FD_UNLIKELY( config->development.no_clone ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration disables multiprocess which is a development only feature" ));
415 0 : if( FD_UNLIKELY( config->development.bench.max_cost_per_block ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration sets [development.bench.max_cost_per_block] which is a development only feature" ));
416 0 : if( FD_UNLIKELY( config->development.bench.max_shreds_per_block ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration sets [development.bench.max_shreds_per_block] which is a development only feature" ));
417 0 : if( FD_UNLIKELY( config->development.bench.disable_blockstore_from_slot ) ) 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" ));
418 0 : if( FD_UNLIKELY( config->development.bench.disable_status_cache ) ) FD_LOG_ERR(( "trying to join a live cluster, but configuration enables [development.bench.disable_status_cache] which is a development only feature" ));
419 0 : }
420 :
421 : /* When running a local cluster, some options are overridden by default
422 : to make starting and running in development environments a little
423 : easier and less strict. */
424 0 : if( FD_UNLIKELY( is_local_cluster ) ) {
425 0 : if( FD_LIKELY( !strcmp( config->paths.vote_account, "" ) ) ) {
426 0 : FD_TEST( fd_cstr_printf_check( config->paths.vote_account,
427 0 : sizeof( config->paths.vote_account ),
428 0 : NULL,
429 0 : "%s/vote-account.json",
430 0 : config->paths.base ) );
431 0 : }
432 :
433 0 : strncpy( config->cluster, "development", sizeof(config->cluster) );
434 0 : }
435 :
436 0 : fd_config_validate_consensus( config );
437 :
438 0 : }
439 :
440 0 : #define CFG_HAS_NON_EMPTY( key ) do { \
441 0 : if( !strnlen( config->key, sizeof(config->key) ) ) { \
442 0 : FD_LOG_ERR(( "missing `%s`", #key )); \
443 0 : } \
444 0 : } while(0)
445 :
446 0 : #define CFG_HAS_NON_ZERO( key ) do { \
447 0 : if( !config->key ) { FD_LOG_ERR(( "missing `%s`", #key )); } \
448 0 : } while(0)
449 :
450 0 : #define CFG_HAS_POW2( key ) do { \
451 0 : ulong value = (ulong)( config -> key ); \
452 0 : if( !value || !fd_ulong_is_pow2( value ) ) { \
453 0 : FD_LOG_ERR(( "`%s` must be a power of 2", #key )); \
454 0 : } \
455 0 : } while(0)
456 :
457 : static void
458 0 : fd_config_validatef( fd_configf_t const * config ) {
459 0 : CFG_HAS_NON_EMPTY( layout.mode );
460 0 : CFG_HAS_NON_ZERO( layout.sign_tile_count );
461 0 : CFG_HAS_NON_ZERO( layout.resolv_tile_count );
462 0 : CFG_HAS_NON_ZERO( layout.execle_tile_count );
463 0 : CFG_HAS_NON_ZERO( layout.snapzp_tile_count );
464 0 : CFG_HAS_NON_ZERO( layout.snapsv_tile_count );
465 0 : CFG_HAS_NON_ZERO( layout.snapsv_io_worker_count );
466 0 : CFG_HAS_NON_ZERO( layout.snapdc_tile_count );
467 0 : CFG_HAS_NON_ZERO( layout.snapin_tile_count );
468 0 : if( FD_UNLIKELY( config->layout.sign_tile_count < 2 ) ) {
469 0 : FD_LOG_ERR(( "layout.sign_tile_count must be >= 2" ));
470 0 : }
471 :
472 0 : if( FD_UNLIKELY( config->snapshots.sources.gossip.allow_any && config->snapshots.sources.gossip.allow_list_cnt>0UL ) ) {
473 0 : FD_LOG_ERR(( "`snapshots.sources.gossip` has an explicit list of %lu allowed peer(s) in `allow_list` "
474 0 : "but also allows any peer with `allow_any=true`. `allow_list` has no effect and may "
475 0 : "give a false sense of security. Please modify one of the two options and restart.",
476 0 : config->snapshots.sources.gossip.allow_list_cnt ));
477 0 : }
478 0 : for( ulong i=0UL; i<config->snapshots.sources.gossip.allow_list_cnt; i++ ) {
479 0 : for( ulong j=0UL; j<config->snapshots.sources.gossip.block_list_cnt; j++ ) {
480 0 : if( FD_UNLIKELY( 0==strcmp( config->snapshots.sources.gossip.allow_list[ i ], config->snapshots.sources.gossip.block_list[ j ] ) ) ) {
481 0 : FD_LOG_ERR(( "`snapshots.sources.gossip` has repeated public key `%s` in both allow[%lu] and block[%lu] lists. "
482 0 : "Please modify one of the two options and restart.", config->snapshots.sources.gossip.allow_list[ i ], i, j ));
483 :
484 0 : }
485 0 : }
486 0 : }
487 :
488 0 : if( FD_UNLIKELY( config->snapshots.server.enabled && !config->layout.enable_snapshot_production ) ) {
489 0 : FD_LOG_ERR(( "[snapshots.server].enabled=true requires [layout].enable_snapshot_production=true" ));
490 0 : }
491 :
492 0 : CFG_HAS_NON_ZERO( snapshots.wait_for_peers_timeout_seconds );
493 0 : if( FD_UNLIKELY( config->snapshots.server.idle_timeout_millis<100UL ||
494 0 : config->snapshots.server.idle_timeout_millis>=60000UL ) ) {
495 0 : FD_LOG_ERR(( "`snapshots.server.idle_timeout_millis` must be in [100,60000)" ));
496 0 : }
497 0 : if( FD_UNLIKELY( config->snapshots.server.send_timeout_millis<100UL ||
498 0 : config->snapshots.server.send_timeout_millis>=60000UL ) ) {
499 0 : FD_LOG_ERR(( "`snapshots.server.send_timeout_millis` must be in [100,60000)" ));
500 0 : }
501 :
502 0 : if( FD_UNLIKELY( config->snapshots.max_full_snapshots_to_keep>FD_SSARCHIVE_MAX_ENTRIES ) ) {
503 0 : FD_LOG_ERR(( "`snapshots.max_full_snapshots_to_keep` is %u but must be at most %lu",
504 0 : config->snapshots.max_full_snapshots_to_keep, FD_SSARCHIVE_MAX_ENTRIES ));
505 0 : }
506 0 : if( FD_UNLIKELY( config->snapshots.max_incremental_snapshots_to_keep>FD_SSARCHIVE_MAX_ENTRIES ) ) {
507 0 : FD_LOG_ERR(( "`snapshots.max_incremental_snapshots_to_keep` is %u but must be at most %lu",
508 0 : config->snapshots.max_incremental_snapshots_to_keep, FD_SSARCHIVE_MAX_ENTRIES ));
509 0 : }
510 :
511 0 : CFG_HAS_NON_ZERO( accounts.max_accounts );
512 0 : CFG_HAS_NON_ZERO( accounts.cache_size_gib );
513 :
514 0 : CFG_HAS_NON_ZERO( development.genesis.max_file_size_mib );
515 0 : if( FD_UNLIKELY( config->development.genesis.max_file_size_mib>FD_GENESIS_MAX_FILE_SIZE_MIB ) ) {
516 0 : FD_LOG_ERR(( "`development.genesis.max_file_size_mib` must be at most %lu", FD_GENESIS_MAX_FILE_SIZE_MIB ));
517 0 : }
518 :
519 0 : CFG_HAS_NON_ZERO( runtime.program_cache_size_mib );
520 0 : }
521 :
522 : void
523 0 : fd_config_validate( fd_config_t const * config ) {
524 0 : fd_config_validatef( &config->firedancer );
525 :
526 0 : CFG_HAS_NON_EMPTY( name );
527 0 : CFG_HAS_NON_EMPTY( paths.base );
528 :
529 0 : CFG_HAS_NON_EMPTY( log.colorize );
530 0 : CFG_HAS_NON_EMPTY( log.level_logfile );
531 0 : CFG_HAS_NON_EMPTY( log.level_stderr );
532 0 : CFG_HAS_NON_EMPTY( log.level_flush );
533 :
534 0 : CFG_HAS_NON_EMPTY( layout.affinity );
535 0 : CFG_HAS_NON_EMPTY( layout.blocklist_cores );
536 0 : CFG_HAS_NON_ZERO ( layout.net_tile_count );
537 0 : CFG_HAS_NON_ZERO ( layout.quic_tile_count );
538 0 : CFG_HAS_NON_ZERO ( layout.verify_tile_count );
539 0 : CFG_HAS_NON_ZERO ( layout.shred_tile_count );
540 :
541 0 : CFG_HAS_NON_EMPTY( hugetlbfs.mount_path );
542 0 : CFG_HAS_NON_EMPTY( hugetlbfs.max_page_size );
543 :
544 0 : CFG_HAS_NON_ZERO( net.ingress_buffer_size );
545 :
546 0 : ulong bench_cost = config->development.bench.max_cost_per_block;
547 0 : ulong bench_shreds = config->development.bench.max_shreds_per_block;
548 0 : if( FD_UNLIKELY( bench_cost && (bench_cost<FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND || bench_cost>=UINT_MAX) ) ) /* fd_pack_limits_t: [0,UINT_MAX) */
549 0 : FD_LOG_ERR(( "invalid [development.bench.max_cost_per_block]: must be 0 or in [%lu,%u)", FD_PACK_MAX_COST_PER_BLOCK_UPPER_BOUND, UINT_MAX ));
550 0 : if( FD_UNLIKELY( bench_shreds && (bench_shreds%FD_SHRED_BLK_MAX || bench_shreds>FD_SHRED_BLK_MAX_RAISED) ) )
551 0 : FD_LOG_ERR(( "invalid [development.bench.max_shreds_per_block]: must be 0 or a multiple of %lu up to %lu", (ulong)FD_SHRED_BLK_MAX, FD_SHRED_BLK_MAX_RAISED ));
552 :
553 0 : if( 0==strcmp( config->net.provider, "xdp" ) ) {
554 0 : if( 0!=strcmp( config->net.xdp.xdp_mode, "skb" ) &&
555 0 : 0!=strcmp( config->net.xdp.xdp_mode, "drv" ) &&
556 0 : 0!=strcmp( config->net.xdp.xdp_mode, "auto" ) &&
557 0 : 0!=strcmp( config->net.xdp.xdp_mode, "default" ) ) {
558 0 : FD_LOG_ERR(( "invalid `net.xdp.xdp_mode`: \"%s\"; must be \"skb\", \"drv\", \"auto\" or \"default\"",
559 0 : config->net.xdp.xdp_mode ));
560 0 : }
561 :
562 0 : if( 0!=strcmp( config->net.xdp.poll_mode, "prefbusy" ) &&
563 0 : 0!=strcmp( config->net.xdp.poll_mode, "softirq" ) &&
564 0 : 0!=strcmp( config->net.xdp.poll_mode, "auto" ) ) {
565 0 : FD_LOG_ERR(( "invalid `net.xdp.poll_mode`: \"%s\"; must be \"prefbusy\", \"softirq\" or \"auto\"",
566 0 : config->net.xdp.poll_mode ));
567 0 : }
568 :
569 0 : CFG_HAS_POW2 ( net.xdp.xdp_rx_queue_size );
570 0 : CFG_HAS_POW2 ( net.xdp.xdp_tx_queue_size );
571 0 : if( 0!=strcmp( config->net.xdp.rss_queue_mode, "dedicated" ) &&
572 0 : 0!=strcmp( config->net.xdp.rss_queue_mode, "simple" ) &&
573 0 : 0!=strcmp( config->net.xdp.rss_queue_mode, "auto" ) ) {
574 0 : FD_LOG_ERR(( "invalid `net.xdp.rss_queue_mode`: \"%s\"; must be \"simple\", \"dedicated\" or \"auto\"",
575 0 : config->net.xdp.rss_queue_mode ));
576 0 : }
577 0 : } else if( 0==strcmp( config->net.provider, "mlx5" ) ) {
578 0 : CFG_HAS_POW2( net.mlx5.rx_queue_size );
579 0 : CFG_HAS_POW2( net.mlx5.tx_queue_size );
580 0 : if( FD_UNLIKELY( config->net.mlx5.rx_queue_size<=FD_MLX5_BATCH_SIZE ||
581 0 : config->net.mlx5.tx_queue_size< FD_MLX5_BATCH_SIZE ) ) {
582 0 : FD_LOG_ERR(( "invalid mlx5 queue depth: RX must exceed %u and TX must be at least %u",
583 0 : FD_MLX5_BATCH_SIZE, FD_MLX5_BATCH_SIZE ));
584 0 : }
585 0 : if( FD_UNLIKELY( config->net.mlx5.rx_queue_size>FD_MLX5_QUEUE_DEPTH_MAX ||
586 0 : config->net.mlx5.tx_queue_size>FD_MLX5_QUEUE_DEPTH_MAX ) ) {
587 0 : FD_LOG_ERR(( "invalid mlx5 queue depth: RX and TX must not exceed %u", FD_MLX5_QUEUE_DEPTH_MAX ));
588 0 : }
589 0 : } else if( 0==strcmp( config->net.provider, "socket" ) ) {
590 0 : CFG_HAS_NON_ZERO( net.socket.receive_buffer_size );
591 0 : CFG_HAS_NON_ZERO( net.socket.send_buffer_size );
592 0 : } else if( 0==strcmp( config->net.provider, "auto" ) ) {
593 : /* "auto" is resolved after interface discovery in fd_config_fill(). */
594 0 : } else {
595 0 : FD_LOG_ERR(( "invalid `net.provider`: \"%s\"; must be \"auto\", \"xdp\", \"socket\" or \"mlx5\"",
596 0 : config->net.provider ));
597 0 : }
598 :
599 0 : CFG_HAS_NON_ZERO( tiles.netlink.max_routes );
600 0 : CFG_HAS_NON_ZERO( tiles.netlink.max_peer_routes );
601 0 : CFG_HAS_NON_ZERO( tiles.netlink.max_neighbors );
602 :
603 0 : CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_connections );
604 0 : CFG_HAS_NON_ZERO( tiles.quic.txn_reassembly_count );
605 0 : CFG_HAS_NON_ZERO( tiles.quic.max_concurrent_handshakes );
606 0 : CFG_HAS_NON_ZERO( tiles.quic.idle_timeout_millis );
607 :
608 0 : CFG_HAS_NON_ZERO( tiles.verify.signature_cache_size );
609 0 : CFG_HAS_NON_ZERO( tiles.verify.receive_buffer_size );
610 :
611 0 : CFG_HAS_NON_ZERO( tiles.dedup.signature_cache_size );
612 :
613 0 : CFG_HAS_NON_ZERO( tiles.pack.max_pending_transactions );
614 :
615 0 : CFG_HAS_NON_ZERO( tiles.shred.max_pending_shred_sets );
616 :
617 0 : CFG_HAS_POW2( tiles.repair.slot_max );
618 :
619 0 : if( FD_UNLIKELY( config->tiles.bundle.keepalive_interval_millis < 3000 ||
620 0 : config->tiles.bundle.keepalive_interval_millis > 3600000 ) ) {
621 0 : FD_LOG_ERR(( "`tiles.bundle.keepalive_interval_millis` must be in range [3000, 3,600,000]" ));
622 0 : }
623 :
624 0 : CFG_HAS_NON_EMPTY( development.core_dump );
625 :
626 0 : CFG_HAS_NON_ZERO( development.genesis.target_tick_duration_micros );
627 0 : CFG_HAS_NON_ZERO( development.genesis.ticks_per_slot );
628 0 : CFG_HAS_NON_ZERO( development.genesis.fund_initial_accounts );
629 0 : CFG_HAS_NON_ZERO( development.genesis.fund_initial_amount_lamports );
630 :
631 0 : CFG_HAS_NON_ZERO ( development.bench.benchg_tile_count );
632 0 : CFG_HAS_NON_ZERO ( development.bench.benchs_tile_count );
633 0 : CFG_HAS_NON_EMPTY( development.bench.affinity );
634 0 : }
635 :
636 : #undef CFG_HAS_NON_EMPTY
637 : #undef CFG_HAS_NON_ZERO
638 : #undef CFG_HAS_POW2
639 :
640 : void
641 : fd_config_load( int is_local_cluster,
642 : char const * default_config,
643 : ulong default_config_sz,
644 : char const * override_config,
645 : char const * override_config_path,
646 : ulong override_config_sz,
647 : char const * user_config,
648 : ulong user_config_sz,
649 : char const * user_config_path,
650 : fd_config_t * config,
651 0 : int dev ) {
652 0 : memset( config, 0, sizeof(config_t) );
653 0 : config->boot_timestamp_nanos = fd_log_wallclock();
654 :
655 0 : FD_STATIC_ASSERT( sizeof(config->launch_cpus)==8UL*fd_cpuset_word_cnt, launch_cpus );
656 0 : if( FD_UNLIKELY( fd_cpuset_getaffinity( 0, (fd_cpuset_t *)config->launch_cpus ) ) ) memset( config->launch_cpus, 0, sizeof(config->launch_cpus) );
657 :
658 : /* Before any tile joins the cpuset isolation cgroup, which carries
659 : none of the service's limits. */
660 0 : char cgroup[ 4096 ];
661 0 : int cgroup_fd = open( "/proc/self/cgroup", O_RDONLY|O_CLOEXEC );
662 0 : long cgroup_sz = cgroup_fd<0 ? -1L : read( cgroup_fd, cgroup, sizeof(cgroup)-1UL );
663 0 : if( FD_LIKELY( cgroup_fd>=0 ) ) close( cgroup_fd );
664 0 : cgroup[ fd_long_max( cgroup_sz, 0L ) ] = '\0';
665 0 : char const * v2 = !strncmp( cgroup, "0::", 3UL ) ? cgroup : strstr( cgroup, "\n0::" );
666 0 : if( FD_LIKELY( v2 ) ) {
667 0 : v2 += v2[ 0 ]=='\n' ? 4UL : 3UL;
668 0 : ulong len = strcspn( v2, "\n" );
669 0 : FD_TEST( fd_cstr_printf_check( config->launch_cgroup, sizeof(config->launch_cgroup), NULL, "%.*s", (int)len, v2 ) );
670 0 : }
671 :
672 0 : fd_config_load_buf( config, default_config, default_config_sz, "default.toml" );
673 0 : fd_config_validate( config );
674 0 : if( FD_UNLIKELY( override_config ) ) {
675 0 : fd_config_load_buf( config, override_config, override_config_sz, override_config_path );
676 0 : fd_config_validate( config );
677 0 : }
678 0 : if( FD_LIKELY( user_config ) ) {
679 0 : fd_config_load_buf( config, user_config, user_config_sz, user_config_path );
680 0 : fd_config_validate( config );
681 0 : if( FD_UNLIKELY( user_config_sz>sizeof(config->user_config)-1UL ) ) {
682 0 : if( FD_UNLIKELY( config->telemetry && config->tiles.event.url[ 0 ] ) ) {
683 0 : FD_LOG_ERR(( "config file (%s) is too large (%lu bytes, max %lu) to report in telemetry", user_config_path, user_config_sz, sizeof(config->user_config)-1UL ));
684 0 : }
685 0 : } else {
686 0 : fd_memcpy( config->user_config, user_config, user_config_sz );
687 0 : config->user_config[ user_config_sz ] = '\0';
688 0 : config->user_config_len = user_config_sz;
689 0 : }
690 0 : }
691 :
692 0 : fd_config_fill( config, is_local_cluster, dev );
693 0 : }
694 :
695 : int
696 0 : fd_config_to_memfd( fd_config_t const * config ) {
697 0 : int config_memfd = memfd_create( "fd_config", 0 );
698 0 : if( FD_UNLIKELY( -1==config_memfd ) ) return -1;
699 0 : if( FD_UNLIKELY( -1==ftruncate( config_memfd, sizeof( config_t ) ) ) ) {
700 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
701 0 : return -1;
702 0 : }
703 :
704 0 : uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ|PROT_WRITE, MAP_SHARED, config_memfd, 0 );
705 0 : if( FD_UNLIKELY( bytes==MAP_FAILED ) ) {
706 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_WARNING(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
707 0 : return -1;
708 0 : }
709 :
710 0 : fd_memcpy( bytes, config, sizeof( config_t ) );
711 0 : if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) {
712 0 : FD_LOG_WARNING(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
713 0 : return -1;
714 0 : }
715 :
716 0 : return config_memfd;
717 0 : }
|