Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_boot.h"
3 :
4 : #include "../fd_config.h"
5 : #include "../fd_action.h"
6 : #include "../../platform/fd_file_util.h"
7 : #include "../../../disco/topo/fd_topo.h"
8 :
9 : #include <errno.h>
10 : #include <unistd.h>
11 : #include <fcntl.h>
12 : #include <sys/mman.h>
13 :
14 : extern action_t * ACTIONS[];
15 : extern fd_topo_run_tile_t * TILES[];
16 :
17 : fd_topo_run_tile_t
18 0 : fdctl_tile_run( fd_topo_tile_t const * tile ) {
19 0 : for( ulong i=0UL; TILES[ i ]; i++ ) {
20 0 : if( !strcmp( tile->name, TILES[ i ]->name ) ) return *TILES[ i ];
21 0 : }
22 0 : FD_LOG_ERR(( "tile `%s` not found", tile->name ));
23 0 : return (fd_topo_run_tile_t){0};
24 0 : }
25 :
26 : static void
27 : copy_config_from_fd( int config_fd,
28 0 : config_t * config ) {
29 0 : uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ, MAP_PRIVATE, config_fd, 0 );
30 0 : if( FD_UNLIKELY( bytes == MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
31 0 : fd_memcpy( config, bytes, sizeof( config_t ) );
32 0 : if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
33 0 : if( FD_UNLIKELY( close( config_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
34 0 : }
35 :
36 : static void
37 : determine_override_config( int * pargc,
38 : char *** pargv,
39 : fd_config_file_t * const * configs,
40 : char const ** override_config,
41 : char const ** override_config_path,
42 0 : ulong * override_config_sz ) {
43 0 : int testnet = fd_env_strip_cmdline_contains( pargc, pargv, "--testnet" );
44 0 : if( FD_UNLIKELY( testnet ) ) {
45 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
46 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "testnet" ) ) ) {
47 0 : *override_config = (char const *)configs[ i ]->data;
48 0 : *override_config_path = configs[ i ]->name;
49 0 : *override_config_sz = configs[ i ]->data_sz;
50 0 : return;
51 0 : }
52 0 : }
53 :
54 0 : if( FD_UNLIKELY( !*override_config ) ) FD_LOG_ERR(( "no testnet config found" ));
55 0 : }
56 :
57 0 : int devnet = fd_env_strip_cmdline_contains( pargc, pargv, "--devnet" );
58 0 : if( FD_UNLIKELY( devnet ) ) {
59 0 : if( FD_UNLIKELY( testnet ) ) FD_LOG_ERR(( "cannot specify both --testnet and --devnet" ));
60 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
61 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "devnet" ) ) ) {
62 0 : *override_config = (char const *)configs[ i ]->data;
63 0 : *override_config_path = configs[ i ]->name;
64 0 : *override_config_sz = configs[ i ]->data_sz;
65 0 : return;
66 0 : }
67 0 : }
68 :
69 0 : if( FD_UNLIKELY( !*override_config ) ) FD_LOG_ERR(( "no devnet config found" ));
70 0 : }
71 :
72 0 : int mainnet = fd_env_strip_cmdline_contains( pargc, pargv, "--mainnet" );
73 0 : if( FD_UNLIKELY( mainnet ) ) {
74 0 : if( FD_UNLIKELY( testnet || devnet ) ) FD_LOG_ERR(( "cannot specify both --testnet or --devnet and --mainnet" ));
75 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
76 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "mainnet" ) ) ) {
77 0 : *override_config = (char const *)configs[ i ]->data;
78 0 : *override_config_path = configs[ i ]->name;
79 0 : *override_config_sz = configs[ i ]->data_sz;
80 0 : return;
81 0 : }
82 0 : }
83 :
84 0 : if( FD_UNLIKELY( !*override_config ) ) FD_LOG_ERR(( "no mainnet config found" ));
85 0 : }
86 :
87 0 : int testnet_jito = fd_env_strip_cmdline_contains( pargc, pargv, "--testnet-jito" );
88 0 : if( FD_UNLIKELY( testnet_jito ) ) {
89 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
90 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "testnet-jito" ) ) ) {
91 0 : *override_config = (char const *)configs[ i ]->data;
92 0 : *override_config_path = configs[ i ]->name;
93 0 : *override_config_sz = configs[ i ]->data_sz;
94 0 : return;
95 0 : }
96 0 : }
97 :
98 0 : if( FD_UNLIKELY( !*override_config ) ) FD_LOG_ERR(( "no testnet-jito config found" ));
99 0 : }
100 :
101 0 : int mainnet_jito = fd_env_strip_cmdline_contains( pargc, pargv, "--mainnet-jito" );
102 0 : if( FD_UNLIKELY( mainnet_jito ) ) {
103 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
104 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "mainnet-jito" ) ) ) {
105 0 : *override_config = (char const *)configs[ i ]->data;
106 0 : *override_config_path = configs[ i ]->name;
107 0 : *override_config_sz = configs[ i ]->data_sz;
108 0 : return;
109 0 : }
110 0 : }
111 :
112 0 : if( FD_UNLIKELY( !*override_config ) ) FD_LOG_ERR(( "no mainnet-jito config found" ));
113 0 : }
114 0 : }
115 :
116 : __attribute__((weak)) void
117 0 : fd_global_options_help( fd_action_help_t * help ) {
118 0 : fd_action_help_arg( help, "--config", "<path>", "Path to a configuration TOML file" );
119 0 : fd_action_help_arg( help, "--mainnet", NULL, "Use Solana mainnet defaults" );
120 0 : fd_action_help_arg( help, "--testnet", NULL, "Use Solana testnet defaults" );
121 0 : fd_action_help_arg( help, "--devnet", NULL, "Use Solana devnet defaults" );
122 0 : fd_action_help_arg( help, "--mainnet-jito", NULL, "Use Solana mainnet defaults with the Jito relayer/bundles" );
123 0 : fd_action_help_arg( help, "--testnet-jito", NULL, "Use Solana testnet defaults with the Jito relayer/bundles" );
124 0 : fd_action_help_arg( help, "--standby", NULL, "Boot as a failover standby under a junk identity" );
125 0 : fd_action_help_arg( help, "--version", NULL, "Show the current software version" );
126 0 : fd_action_help_arg( help, "--help/-h", NULL, "Print this help message" );
127 0 : }
128 :
129 : int
130 : fd_main_init( int * pargc,
131 : char *** pargv,
132 : config_t * config,
133 : const char * opt_user_config_path,
134 : int is_local_cluster,
135 : int no_topo,
136 : char const * log_path,
137 : fd_config_file_t * const * configs,
138 0 : int dev ) {
139 0 : fd_log_enable_unclean_exit(); /* Don't call atexit handlers on FD_LOG_ERR */
140 0 : fd_log_level_core_set( 5 ); /* Don't dump core for FD_LOG_ERR during boot */
141 0 : fd_log_colorize_set( fd_log_should_colorize() ); /* Colorize during boot until we can determine from config */
142 0 : fd_log_level_stderr_set( 2 ); /* Only NOTICE and above will be logged during boot until fd_log is initialized */
143 :
144 0 : int config_fd = fd_env_strip_cmdline_int( pargc, pargv, "--config-fd", NULL, -1 );
145 :
146 0 : fd_memset( config, 0, sizeof( config_t ) );
147 0 : char * thread = "";
148 0 : if( FD_UNLIKELY( config_fd >= 0 ) ) {
149 0 : copy_config_from_fd( config_fd, config );
150 : /* tick_per_ns needs to be synchronized across processes so that
151 : they can coordinate on metrics measurement. */
152 0 : fd_tempo_set_tick_per_ns( config->tick_per_ns_mu, config->tick_per_ns_sigma );
153 0 : } else {
154 0 : char * user_config = NULL;
155 0 : ulong user_config_sz = 0UL;
156 0 : if( FD_LIKELY( opt_user_config_path ) ) {
157 0 : user_config = fd_file_util_read_all( opt_user_config_path, &user_config_sz );
158 0 : if( FD_UNLIKELY( user_config==MAP_FAILED ) ) FD_LOG_ERR(( "failed to read user config file `%s` (%d-%s)", opt_user_config_path, errno, fd_io_strerror( errno ) ));
159 0 : }
160 :
161 0 : char const * default_config = NULL;
162 0 : ulong default_config_sz = 0UL;
163 0 : for( ulong i=0UL; configs[ i ]; i++ ) {
164 0 : if( FD_UNLIKELY( !strcmp( configs[ i ]->name, "default" ) ) ) {
165 0 : default_config = (char const *)configs[ i ]->data;
166 0 : default_config_sz = configs[ i ]->data_sz;
167 0 : break;
168 0 : }
169 0 : }
170 0 : if( FD_UNLIKELY( !default_config ) ) FD_LOG_ERR(( "no default config found" ));
171 :
172 0 : char const * override_config = NULL;
173 0 : char const * override_config_path = NULL;
174 0 : ulong override_config_sz = 0UL;
175 0 : determine_override_config( pargc, pargv, configs,
176 0 : &override_config, &override_config_path, &override_config_sz );
177 0 : int standby = fd_env_strip_cmdline_contains( pargc, pargv, "--standby" );
178 :
179 : /* Only tiles consume the tick rate, skip calibrating it. */
180 0 : if( FD_UNLIKELY( no_topo ) ) fd_tempo_set_tick_per_ns( 0., 0. );
181 :
182 0 : fd_config_load( is_local_cluster, default_config, default_config_sz, override_config, override_config_path, override_config_sz, user_config, user_config_sz, opt_user_config_path, config, dev );
183 :
184 : /* --standby is a shorthand for [failover.boot_to_standby], applied
185 : before the topology picks the key every tile boots under. */
186 0 : if( FD_UNLIKELY( standby ) ) {
187 0 : if( FD_UNLIKELY( !config->firedancer.failover.enabled ) ) FD_LOG_ERR(( "--standby needs [failover.enabled] set to true" ));
188 0 : config->firedancer.failover.boot_to_standby = 1;
189 0 : }
190 :
191 0 : if( FD_UNLIKELY( user_config && -1==munmap( user_config, user_config_sz ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
192 :
193 0 : config->log.log_fd = -1;
194 0 : config->has_user_config = !!opt_user_config_path;
195 0 : thread = "main";
196 0 : if( FD_UNLIKELY( log_path ) )
197 0 : strncpy( config->log.path, log_path, sizeof( config->log.path ) - 1 );
198 0 : }
199 :
200 0 : char * shmem_args[ 3 ];
201 : /* pass in --shmem-path value from the config */
202 0 : shmem_args[ 0 ] = "--shmem-path";
203 0 : shmem_args[ 1 ] = config->hugetlbfs.mount_path;
204 0 : shmem_args[ 2 ] = NULL;
205 0 : char ** argv = shmem_args;
206 0 : int argc = 2;
207 :
208 0 : ulong pid = fd_sandbox_getpid(); /* Need to read /proc since we might be in a PID namespace now */;
209 :
210 0 : log_path = config->log.path;
211 0 : if( FD_LIKELY( config->log.path[ 0 ]=='\0' ) ) log_path = NULL;
212 :
213 : /* Switch to the sandbox uid/gid for log file creation, so it's always
214 : owned by that user. */
215 :
216 0 : gid_t gid = getgid();
217 0 : uid_t uid = getuid();
218 0 : if( FD_LIKELY( !gid && setegid( config->gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
219 0 : if( FD_LIKELY( !uid && seteuid( config->uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
220 :
221 0 : int boot_silent = config_fd>=0;
222 0 : fd_log_private_boot_custom( 0UL,
223 0 : config->name,
224 0 : 0UL, /* Thread ID will be initialized later */
225 0 : thread, /* Thread will be initialized later */
226 0 : 0UL,
227 0 : config->hostname,
228 0 : fd_log_private_cpu_id_default(),
229 0 : NULL,
230 0 : pid,
231 0 : NULL,
232 0 : pid,
233 0 : config->uid,
234 0 : config->user,
235 0 : 1,
236 0 : config->log.colorize1,
237 0 : boot_silent ? 2 : config->log.level_logfile1,
238 0 : boot_silent ? 2 : config->log.level_stderr1,
239 0 : config->log.level_flush1,
240 0 : 5,
241 0 : config->log.log_fd,
242 0 : log_path );
243 :
244 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
245 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
246 :
247 0 : config->log.log_fd = fd_log_private_logfile_fd();
248 0 : fd_shmem_private_boot( &argc, &argv );
249 0 : fd_tile_private_boot( 0, NULL );
250 :
251 0 : fd_log_level_logfile_set( config->log.level_logfile1 );
252 0 : fd_log_level_stderr_set( config->log.level_stderr1 );
253 0 : fd_log_level_flush_set( config->log.level_flush1 );
254 :
255 : /* When auto config info/decisions are collected is before when the
256 : log file is setup, so the logging of this info must be done separately. */
257 0 : if( !boot_silent ) FD_LOG_INFO(( "%s", config->auto_config_log ));
258 :
259 : #if !FD_HAS_SSE
260 : if( !boot_silent ) FD_LOG_WARNING(( "this CPU architecture is unsupported, expect bugs" ));
261 : #endif
262 :
263 0 : return config_fd<0 && !no_topo;
264 0 : }
265 :
266 : static config_t config;
267 :
268 : int
269 : fd_main( int argc,
270 : char ** _argv,
271 : fd_config_file_t * const * configs,
272 0 : void (* topo_init )( config_t * config ) ) {
273 0 : fd_version_private_boot( &argc, &_argv );
274 0 : char ** argv = _argv;
275 0 : argc--; argv++;
276 :
277 : /* Short circuit evaluating help and version commands so that we don't
278 : need to load and evaluate the entire config file to run them.
279 : This is useful for some operators in CI environments where, for
280 : example, they want to show the version or validate the produced
281 : binary without yet setting up the full TOML. */
282 :
283 0 : action_t * help_action = NULL;
284 0 : for( ulong i=0UL; ACTIONS[ i ]; i++ ) {
285 0 : if( FD_UNLIKELY( ACTIONS[ i ]->is_help ) ) {
286 0 : help_action = ACTIONS[ i ];
287 0 : break;
288 0 : }
289 0 : }
290 :
291 0 : if( FD_UNLIKELY( !argc ) ) {
292 0 : help_action->fn( NULL, NULL );
293 0 : FD_LOG_WARNING(( "no subcommand specified, exiting" ));
294 0 : return 1;
295 0 : }
296 :
297 : /* We need to strip away (potentially leading) cmdline flags first,
298 : since the parser assumes the action is the leading argument */
299 0 : const char * opt_user_config_path = fd_env_strip_cmdline_cstr(
300 0 : &argc,
301 0 : &argv,
302 0 : "--config",
303 0 : "FIREDANCER_CONFIG_TOML",
304 0 : NULL );
305 :
306 0 : action_t * action = NULL;
307 0 : for( ulong i=0UL; ACTIONS[ i ]; i++ ) {
308 0 : if( FD_UNLIKELY( !strcmp( argv[ 0 ], ACTIONS[ i ]->name ) ||
309 0 : (!strcmp( argv[ 0 ], "--version" ) && !strcmp( "version", ACTIONS[ i ]->name )) ||
310 0 : ((!strcmp( argv[ 0 ], "--help" ) || !strcmp( argv[ 0 ], "-h" )) && !strcmp( "help", ACTIONS[ i ]->name ))
311 0 : ) ) {
312 0 : action = ACTIONS[ i ];
313 0 : if( FD_UNLIKELY( action->is_immediate ) ) {
314 0 : action->fn( NULL, NULL );
315 0 : return 0;
316 0 : }
317 0 : break;
318 0 : }
319 0 : }
320 :
321 0 : if( FD_UNLIKELY( action ) ) {
322 0 : int help_argc = argc-1; char ** help_argv = argv+1;
323 0 : if( FD_UNLIKELY( fd_env_strip_cmdline_contains( &help_argc, &help_argv, "--help" ) || fd_env_strip_cmdline_contains( &help_argc, &help_argv, "-h" ) ) ) {
324 0 : fd_action_help_print( action );
325 0 : return 0;
326 0 : }
327 0 : }
328 :
329 0 : int is_local_cluster = action ? action->is_local_cluster : 0;
330 0 : int no_topo = action ? action->no_topo : 0;
331 0 : int load_topo = fd_main_init( &argc, &argv, &config, opt_user_config_path, is_local_cluster, no_topo, NULL, configs, 0 /* dev */ );
332 0 : if( FD_LIKELY( load_topo && action ) ) fd_cstr_ncpy( config.action, action->name, sizeof( config.action ) );
333 0 : if( FD_LIKELY( load_topo ) ) topo_init( &config );
334 :
335 0 : if( FD_UNLIKELY( !action ) ) {
336 0 : help_action->fn( NULL, NULL );
337 0 : FD_LOG_ERR(( "unknown subcommand `%s`", argv[ 0 ] ));
338 0 : }
339 :
340 0 : if( FD_UNLIKELY( action->require_config && !opt_user_config_path ) ) FD_LOG_ERR(( "missing required `--config` argument" ));
341 :
342 0 : argc--; argv++;
343 :
344 0 : args_t args = {0};
345 0 : if( FD_LIKELY( action->args ) ) action->args( &argc, &argv, &args );
346 0 : if( FD_UNLIKELY( argc ) ) FD_LOG_ERR(( "unknown argument `%s`", argv[ 0 ] ));
347 :
348 0 : if( FD_LIKELY( action->perm ) ) {
349 0 : fd_cap_chk_t * chk = fd_cap_chk_join( fd_cap_chk_new( __builtin_alloca_with_align( fd_cap_chk_footprint(), FD_CAP_CHK_ALIGN ) ) );
350 :
351 0 : action->perm( &args, chk, &config );
352 :
353 0 : ulong err_cnt = fd_cap_chk_err_cnt( chk );
354 0 : if( FD_UNLIKELY( err_cnt ) ) {
355 0 : for( ulong i=0UL; i<err_cnt; i++ ) FD_LOG_WARNING(( "%s", fd_cap_chk_err( chk, i ) ));
356 :
357 0 : if( FD_LIKELY( action->permission_err ) ) FD_LOG_ERR(( action->permission_err, action->name ));
358 0 : else FD_LOG_ERR(( "insufficient permissions to execute command `%s`", action->name ));
359 0 : }
360 0 : }
361 :
362 0 : action->fn( &args, &config );
363 :
364 0 : return 0;
365 0 : }
|