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