Line data Source code
1 : #define _GNU_SOURCE
2 : #include "run.h"
3 : #include "../../../../flamenco/accdb/fd_accdb.h"
4 : #include "../../../../flamenco/stakes/fd_epoch_credits.h"
5 : #include "../../../../flamenco/runtime/fd_cost_tracker_store.h"
6 : #include "../../../../flamenco/stakes/fd_vote_stakes.h"
7 : #include "../../../../flamenco/stakes/fd_stake_delegations.h"
8 : #include "../../../../flamenco/runtime/fd_txncache.h"
9 : #include "../../../../disco/store/fd_store.h"
10 :
11 : #include <sys/wait.h>
12 : #include "generated/main_seccomp.h"
13 : #if defined(__aarch64__) || defined(__riscv)
14 : #include "generated/pidns_no_poll_seccomp.h"
15 : #else
16 : #include "generated/pidns_seccomp.h"
17 : #endif
18 :
19 : #include "../../fd_bootinfo.h"
20 : #include "../../../platform/fd_sys_util.h"
21 : #include "../../../platform/fd_file_util.h"
22 : #include "../../../platform/fd_net_util.h"
23 : #include "../../../../disco/net/fd_net_tile.h"
24 : #include "../../../../discof/backup/fd_backup.h"
25 : #include "../../../../discof/backup/fd_snap_pool.h"
26 : #include "../../../../discof/restore/utils/fd_ssarchive.h"
27 : #include "../../../../disco/waker/fd_waker.h"
28 : #include "../../../../disco/sleep/fd_sleep.h"
29 : #include "../../../../util/pod/fd_pod_format.h"
30 : #include "../../../../discof/restore/utils/fd_wfs.h"
31 : #include "../../../../disco/keyguard/fd_keyload.h"
32 :
33 : #include "../configure/configure.h"
34 : #include "../configure/fd_cpu_isolation.h"
35 : #include "../../../../disco/topo/fd_cpu_topo.h"
36 :
37 : #include <dirent.h>
38 : #include <sched.h>
39 : #include <stdio.h>
40 : #include <stdlib.h> /* getenv */
41 : #include <poll.h>
42 : #include <unistd.h>
43 : #include <errno.h>
44 : #include <fcntl.h>
45 : #include <sys/prctl.h>
46 : #include <sys/resource.h>
47 : #include <sys/mman.h>
48 : #include <sys/stat.h>
49 : #include <linux/capability.h>
50 :
51 : #if defined(__x86_64__)
52 : #include <cpuid.h>
53 : #endif
54 :
55 : #include "../../../../util/tile/fd_tile_private.h"
56 :
57 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
58 :
59 0 : #define NAME "run"
60 :
61 : void
62 : run_cmd_perm( args_t * args,
63 : fd_cap_chk_t * chk,
64 0 : config_t const * config ) {
65 0 : (void)args;
66 :
67 0 : ulong mlock_limit = fd_topo_mlock_max_tile( &config->topo );
68 :
69 0 : fd_cap_chk_raise_rlimit( chk, NAME, RLIMIT_MEMLOCK, mlock_limit, "call `rlimit(2)` to increase `RLIMIT_MEMLOCK` so all memory can be locked with `mlock(2)`" );
70 0 : fd_cap_chk_raise_rlimit( chk, NAME, RLIMIT_NICE, 40, "call `setpriority(2)` to increase thread priorities" );
71 0 : fd_cap_chk_raise_rlimit( chk, NAME, RLIMIT_NOFILE, CONFIGURE_NR_OPEN_FILES,
72 0 : "call `rlimit(2) to increase `RLIMIT_NOFILE` to allow more open files" );
73 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_RAW, "call `socket(2)` to bind to a raw socket for use by XDP" );
74 0 : fd_cap_chk_cap( chk, NAME, CAP_SYS_ADMIN, "call `bpf(2)` with the `BPF_OBJ_GET` command to initialize XDP" );
75 0 : if( fd_sandbox_requires_cap_sys_admin( config->uid, config->gid ) )
76 0 : fd_cap_chk_cap( chk, NAME, CAP_SYS_ADMIN, "call `unshare(2)` with `CLONE_NEWUSER` to sandbox the process in a user namespace" );
77 0 : if( FD_LIKELY( getuid() != config->uid ) )
78 0 : fd_cap_chk_cap( chk, NAME, CAP_SETUID, "call `setresuid(2)` to switch uid to the sandbox user" );
79 0 : if( FD_LIKELY( getgid()!=config->gid ) )
80 0 : fd_cap_chk_cap( chk, NAME, CAP_SETGID, "call `setresgid(2)` to switch gid to the sandbox user" );
81 0 : if( FD_UNLIKELY( config->tiles.metric.prometheus_listen_port<1024 ) )
82 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving metrics" );
83 0 : if( FD_UNLIKELY( config->tiles.gui.gui_listen_port<1024 ) )
84 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving the GUI" );
85 0 : if( FD_UNLIKELY( config->firedancer.failover.enabled && config->firedancer.failover.listen_port<1024 ) )
86 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for the failover listener" );
87 0 : }
88 :
89 : struct pidns_clone_args {
90 : config_t const * config;
91 : int * pipefd;
92 : int closefd;
93 : };
94 :
95 : extern char fd_log_private_path[ 1024 ]; /* empty string on start */
96 :
97 : static pid_t pid_namespace;
98 :
99 0 : #define FD_LOG_ERR_NOEXIT(a) do { long _fd_log_msg_now = fd_log_wallclock(); fd_log_private_1( 4, _fd_log_msg_now, __FILE__, __LINE__, __func__, fd_log_private_0 a ); } while(0)
100 :
101 : static void
102 0 : parent_signal( int sig ) {
103 0 : if( FD_LIKELY( pid_namespace ) ) kill( pid_namespace, SIGKILL );
104 :
105 0 : if( -1!=fd_log_private_logfile_fd() ) FD_LOG_ERR_NOEXIT(( "Received signal %s%s%s %s(%s)%s\n%sLog at \"%s\"%s", fd_log_style_bold(), fd_io_strsignal_name( sig ), fd_log_style_normal(), fd_log_style_dim(), fd_io_strsignal_desc( sig ), fd_log_style_normal(), fd_log_style_dim(), fd_log_private_path, fd_log_style_normal() ));
106 0 : else FD_LOG_ERR_NOEXIT(( "Received signal %s%s%s %s(%s)%s", fd_log_style_bold(), fd_io_strsignal_name( sig ), fd_log_style_normal(), fd_log_style_dim(), fd_io_strsignal_desc( sig ), fd_log_style_normal() ));
107 :
108 0 : if( FD_LIKELY( sig==SIGINT ) ) fd_sys_util_exit_group( 128+SIGINT );
109 0 : else fd_sys_util_exit_group( 0 );
110 0 : }
111 :
112 : static void
113 0 : install_parent_signals( void ) {
114 0 : struct sigaction sa = {
115 0 : .sa_handler = parent_signal,
116 0 : .sa_flags = 0,
117 0 : };
118 0 : if( FD_UNLIKELY( sigaction( SIGTERM, &sa, NULL ) ) )
119 0 : FD_LOG_ERR(( "sigaction(SIGTERM) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
120 0 : if( FD_UNLIKELY( sigaction( SIGINT, &sa, NULL ) ) )
121 0 : FD_LOG_ERR(( "sigaction(SIGINT) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
122 :
123 0 : sa.sa_handler = SIG_IGN;
124 0 : if( FD_UNLIKELY( sigaction( SIGUSR1, &sa, NULL ) ) )
125 0 : FD_LOG_ERR(( "sigaction(SIGUSR1) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
126 0 : if( FD_UNLIKELY( sigaction( SIGUSR2, &sa, NULL ) ) )
127 0 : FD_LOG_ERR(( "sigaction(SIGUSR2) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
128 0 : }
129 :
130 : void *
131 0 : create_clone_stack( void ) {
132 0 : ulong mmap_sz = FD_TILE_PRIVATE_STACK_SZ + 2UL*FD_SHMEM_NORMAL_PAGE_SZ;
133 0 : uchar * stack = (uchar *)mmap( NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0 );
134 0 : if( FD_UNLIKELY( stack==MAP_FAILED ) )
135 0 : FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
136 :
137 : /* Make space for guard lo and guard hi */
138 0 : if( FD_UNLIKELY( munmap( stack, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
139 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
140 0 : stack += FD_SHMEM_NORMAL_PAGE_SZ;
141 0 : if( FD_UNLIKELY( munmap( stack + FD_TILE_PRIVATE_STACK_SZ, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
142 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
143 :
144 : /* Create the guard regions in the extra space */
145 0 : void * guard_lo = (void *)(stack - FD_SHMEM_NORMAL_PAGE_SZ );
146 0 : if( FD_UNLIKELY( mmap( guard_lo, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
147 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_lo ) )
148 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
149 :
150 0 : void * guard_hi = (void *)(stack + FD_TILE_PRIVATE_STACK_SZ);
151 0 : if( FD_UNLIKELY( mmap( guard_hi, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
152 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_hi ) )
153 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
154 :
155 0 : return stack;
156 0 : }
157 :
158 : static int
159 0 : cgroup_procs_write( char const * path ) {
160 0 : int fd = open( path, O_WRONLY );
161 0 : if( FD_UNLIKELY( fd<0 ) ) {
162 0 : if( FD_LIKELY( errno==ENOENT ) ) return 0;
163 0 : FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
164 0 : }
165 :
166 0 : char pid[ 32 ];
167 0 : ulong pid_len;
168 0 : FD_TEST( fd_cstr_printf_check( pid, sizeof(pid), &pid_len, "%ld", (long)getpid() ) );
169 0 : if( FD_UNLIKELY( write( fd, pid, pid_len )!=(long)pid_len ) )
170 0 : FD_LOG_ERR(( "write(%s,\"%s\") failed (%i-%s)", path, pid, errno, fd_io_strerror( errno ) ));
171 0 : if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
172 0 : return 1;
173 0 : }
174 :
175 : struct spawn_cgroup {
176 : int probed; /* isolation cgroup existence checked, original saved */
177 : int present; /* isolation cgroup exists */
178 : int joined; /* currently a member of the isolation cgroup */
179 : char isolation[ PATH_MAX ];
180 : char original[ PATH_MAX ];
181 : };
182 :
183 : static void
184 : join_isolation_cgroup( char const * name,
185 0 : struct spawn_cgroup * cg ) {
186 0 : if( FD_UNLIKELY( !cg->probed ) ) {
187 0 : cg->probed = 1;
188 0 : FD_TEST( fd_cstr_printf_check( cg->isolation, sizeof(cg->isolation), NULL, "/sys/fs/cgroup/%s/cgroup.procs", name ) );
189 :
190 : /* The cpuset stage not being configured (no cgroup) is the common
191 : case and must be decided FIRST: on cgroup v1-only or hybrid
192 : hosts /proc/self/cgroup does not have the v2 format, and
193 : validating it before knowing the stage is even in use would
194 : turn every tile launch on such hosts into a fatal error. */
195 0 : cg->present = !access( cg->isolation, F_OK );
196 0 : if( FD_LIKELY( !cg->present ) ) return;
197 :
198 : /* Remember where we came from. The v2 entry in /proc/self/cgroup
199 : is the line "0::<path>"; on a pure v2 hierarchy it is the only
200 : line, but on hybrid systems v1 controller lines precede it, so
201 : search rather than assume. */
202 0 : char buf[ 4096 ];
203 0 : int fd = open( "/proc/self/cgroup", O_RDONLY );
204 0 : if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
205 0 : long n = read( fd, buf, sizeof(buf)-1UL );
206 0 : if( FD_UNLIKELY( n<0L ) ) FD_LOG_ERR(( "read(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
207 0 : if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
208 0 : buf[ n ] = '\0';
209 :
210 0 : char * line = buf;
211 0 : while( line && strncmp( line, "0::", 3UL ) ) {
212 0 : line = strchr( line, '\n' );
213 0 : if( FD_LIKELY( line ) ) line++;
214 0 : }
215 0 : if( FD_UNLIKELY( !line || !line[ 0 ] ) )
216 0 : FD_LOG_ERR(( "no cgroup v2 entry in /proc/self/cgroup while the cpuset isolation cgroup `/sys/fs/cgroup/%s` "
217 0 : "exists. Remove it with `%s configure fini cpuset`", name, FD_BINARY_NAME ));
218 0 : char * nl = strchr( line, '\n' ); if( FD_LIKELY( nl ) ) *nl = '\0';
219 0 : FD_TEST( fd_cstr_printf_check( cg->original, PATH_MAX, NULL,
220 0 : "/sys/fs/cgroup%s/cgroup.procs", line+3UL ) );
221 0 : }
222 :
223 0 : if( FD_UNLIKELY( !cg->present || cg->joined ) ) return;
224 0 : cg->joined = cgroup_procs_write( cg->isolation );
225 0 : }
226 :
227 : static void
228 0 : leave_isolation_cgroup( struct spawn_cgroup * cg ) {
229 0 : if( FD_LIKELY( !cg->joined ) ) return;
230 0 : if( FD_UNLIKELY( !cgroup_procs_write( cg->original ) ) ) FD_LOG_ERR(( "could not return to original cgroup `%s`", cg->original ));
231 0 : cg->joined = 0;
232 0 : }
233 :
234 : static pid_t
235 : execve_tile( char const * name,
236 : fd_topo_tile_t const * tile,
237 : fd_cpuset_t const * float_cpu_set,
238 : fd_cpuset_t const * cpuless_cpu_set,
239 : int floating_priority,
240 : int config_memfd,
241 : int pipefd,
242 0 : struct spawn_cgroup * cg ) {
243 0 : FD_CPUSET_DECL( cpu_set );
244 0 : if( FD_LIKELY( tile->cpu_idx!=ULONG_MAX ) ) {
245 : /* Join the cpuset isolation cgroup (if configured) and set the
246 : thread affinity before we clone the new process, to ensure
247 : kernel first touch happens on the desired thread. The child
248 : inherits both. */
249 0 : join_isolation_cgroup( name, cg );
250 0 : if( FD_UNLIKELY( tile->floats ) ) {
251 : /* the floating CPUs on this tile's NUMA node: memory was placed
252 : by cpu_idx */
253 0 : ulong numa_idx = fd_shmem_numa_idx( tile->cpu_idx );
254 0 : for( ulong cpu=0UL; cpu<FD_TILE_MAX; cpu++ )
255 0 : if( fd_cpuset_test( float_cpu_set, cpu ) && fd_shmem_numa_idx( cpu )==numa_idx ) fd_cpuset_insert( cpu_set, cpu );
256 0 : if( FD_UNLIKELY( !fd_cpuset_cnt( cpu_set ) ) ) fd_memcpy( cpu_set, float_cpu_set, fd_cpuset_footprint() );
257 0 : }
258 0 : if( FD_UNLIKELY( !fd_cpuset_cnt( cpu_set ) ) ) fd_cpuset_insert( cpu_set, tile->cpu_idx );
259 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, -19 ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
260 0 : } else {
261 0 : leave_isolation_cgroup( cg );
262 0 : fd_cpuset_copy( cpu_set, cpuless_cpu_set );
263 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, floating_priority ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
264 0 : }
265 :
266 0 : if( FD_UNLIKELY( fd_cpuset_setaffinity( 0, cpu_set ) ) ) {
267 0 : if( FD_LIKELY( errno==EINVAL ) ) {
268 0 : FD_LOG_ERR(( "Unable to set the thread affinity for tile %s:%lu on cpu %lu. It is likely that the affinity "
269 0 : "you have specified for this tile in [layout.affinity] of your configuration file contains a "
270 0 : "CPU (%lu) which does not exist on this machine.",
271 0 : tile->name, tile->kind_id, tile->cpu_idx, tile->cpu_idx ));
272 0 : } else {
273 0 : FD_LOG_ERR(( "sched_setaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
274 0 : }
275 0 : }
276 :
277 : /* Clear CLOEXEC on the side of the pipe we want to pass to the tile. */
278 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
279 0 : pid_t child = fork();
280 0 : if( FD_UNLIKELY( -1==child ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
281 0 : if( FD_LIKELY( !child ) ) {
282 0 : if( !strcmp( tile->name, "sign" ) && tile->sign.failover_junk_fd!=-1 ) {
283 0 : if( FD_UNLIKELY( -1==fcntl( tile->sign.failover_junk_fd, F_SETFD, 0 ) ) )
284 0 : FD_LOG_ERR(( "passing the failover junk key pipe failed (%i-%s)", errno, fd_io_strerror( errno ) ));
285 0 : }
286 :
287 0 : char _current_executable_path[ PATH_MAX ];
288 0 : FD_TEST( -1!=fd_file_util_self_exe( _current_executable_path ) );
289 :
290 0 : char kind_id[ 32 ], config_fd[ 32 ], pipe_fd[ 32 ];
291 0 : FD_TEST( fd_cstr_printf_check( kind_id, sizeof( kind_id ), NULL, "%lu", tile->kind_id ) );
292 0 : FD_TEST( fd_cstr_printf_check( config_fd, sizeof( config_fd ), NULL, "%d", config_memfd ) );
293 0 : FD_TEST( fd_cstr_printf_check( pipe_fd, sizeof( pipe_fd ), NULL, "%d", pipefd ) );
294 0 : char const * args[ 9 ] = { _current_executable_path, "run1", tile->name, kind_id, "--pipe-fd", pipe_fd, "--config-fd", config_fd, NULL };
295 0 : if( FD_UNLIKELY( -1==execve( _current_executable_path, (char **)args, NULL ) ) ) FD_LOG_ERR(( "execve() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
296 0 : } else {
297 0 : if( FD_UNLIKELY( -1==fcntl( pipefd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
298 0 : return child;
299 0 : }
300 0 : return 0;
301 0 : }
302 :
303 : int
304 0 : main_pid_namespace( void * _args ) {
305 0 : struct pidns_clone_args * args = _args;
306 0 : if( FD_UNLIKELY( close( args->pipefd[ 0 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
307 0 : if( FD_UNLIKELY( -1!=args->closefd ) ) {
308 0 : if( FD_UNLIKELY( close( args->closefd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
309 0 : }
310 :
311 0 : config_t const * config = args->config;
312 :
313 0 : fd_log_thread_set( "pidns" );
314 0 : ulong pid = fd_sandbox_getpid(); /* Need to read /proc again.. we got a new PID from clone */
315 0 : fd_log_private_group_id_set( pid );
316 0 : fd_log_private_thread_id_set( pid );
317 0 : fd_log_private_stack_discover( FD_TILE_PRIVATE_STACK_SZ,
318 0 : &fd_tile_private_stack0, &fd_tile_private_stack1 );
319 :
320 0 : if( FD_UNLIKELY( !config->development.sandbox ) ) {
321 : /* If no sandbox, then there's no actual PID namespace so we can't
322 : wait() grandchildren for the exit code. Do this as a workaround. */
323 0 : if( FD_UNLIKELY( -1==prctl( PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0 ) ) )
324 0 : FD_LOG_ERR(( "prctl(PR_SET_CHILD_SUBREAPER) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
325 0 : }
326 :
327 : /* Save the current affinity, it will be restored after creating any child tiles */
328 0 : FD_CPUSET_DECL( floating_cpu_set );
329 0 : if( FD_UNLIKELY( fd_cpuset_getaffinity( 0, floating_cpu_set ) ) )
330 0 : FD_LOG_ERR(( "fd_cpuset_getaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
331 :
332 0 : FD_CPUSET_DECL( float_cpu_set );
333 0 : FD_CPUSET_DECL( cpuless_cpu_set );
334 0 : static fd_topo_cpus_t cpus[1];
335 0 : fd_topo_cpus_init( cpus );
336 0 : fd_topo_cpus_floating( cpus, &config->topo, floating_cpu_set, float_cpu_set, cpuless_cpu_set );
337 0 : int any_floats = 0;
338 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) any_floats |= config->topo.tiles[ i ].floats;
339 :
340 0 : pid_t child_pids[ FD_TOPO_MAX_TILES+1 ];
341 0 : ulong actual_pids[ FD_TOPO_MAX_TILES+1 ];
342 0 : for( ulong i=0UL; i<FD_TOPO_MAX_TILES+1; i++ ) actual_pids[ i ] = ULONG_MAX;
343 0 : char child_names[ FD_TOPO_MAX_TILES+1 ][ 32 ];
344 0 : ulong child_idxs[ FD_TOPO_MAX_TILES+1 ];
345 0 : struct pollfd fds[ FD_TOPO_MAX_TILES+2 ];
346 :
347 0 : int config_memfd = fd_config_to_memfd( config );
348 0 : if( FD_UNLIKELY( -1==config_memfd ) ) FD_LOG_ERR(( "fd_config_to_memfd() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
349 :
350 0 : int need_iavf = 0==strcmp( config->net.provider, "iavf" );
351 0 : fd_iavf_fds_t iavf_fds = { .member_cnt=0UL };
352 0 : for( ulong i=0UL; i<FD_IAVF_MEMBER_MAX; i++ ) {
353 0 : iavf_fds.container_fd[ i ] = -1;
354 0 : iavf_fds.group_fd [ i ] = -1;
355 0 : iavf_fds.device_fd [ i ] = -1;
356 0 : }
357 0 : if( need_iavf ) {
358 0 : fd_topo_install_iavf( (fd_topo_t *)&config->topo, &iavf_fds );
359 0 : }
360 :
361 0 : int need_mlx5 = 0==strcmp( config->net.provider, "mlx5" );
362 0 : fd_mlx5_fds_t mlx5_fds = { .cmd_fd=-1, .async_fd=-1 };
363 0 : if( need_mlx5 ) {
364 0 : fd_topo_install_mlx5( (fd_topo_t *)&config->topo, &mlx5_fds );
365 0 : }
366 :
367 0 : ulong child_cnt = 0UL;
368 :
369 0 : errno = 0;
370 0 : int save_priority = getpriority( PRIO_PROCESS, 0 );
371 0 : if( FD_UNLIKELY( -1==save_priority && errno ) ) FD_LOG_ERR(( "getpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
372 :
373 0 : int need_xdp = 0==strcmp( config->net.provider, "xdp" );
374 0 : fd_xdp_fds_t xdp_fds[ FD_TOPO_XDP_FDS_MAX ];
375 0 : uint xdp_fds_cnt = FD_TOPO_XDP_FDS_MAX;
376 0 : if( need_xdp ) {
377 0 : fd_topo_install_xdp( &config->topo, xdp_fds, &xdp_fds_cnt, config->net.bind_address_parsed, 0 );
378 0 : }
379 :
380 0 : initialize_accdb_fd( config );
381 0 : initialize_epoch_credits_fd( config );
382 0 : initialize_cost_tracker_fd( config );
383 0 : initialize_vote_stakes_fd( config );
384 0 : initialize_stake_delegations_fd( config );
385 0 : initialize_txncache_fd( config );
386 0 : initialize_store_fds( config );
387 0 : ulong store_obj_id = fd_pod_query_ulong( config->topo.props, "store", ULONG_MAX );
388 0 : int has_store = store_obj_id!=ULONG_MAX;
389 0 : ulong snap_max = 0UL;
390 0 : ulong snap_retained_max = 0UL;
391 0 : int snapshot_upload_enabled = 0;
392 0 : int snapshot_dio_enabled = 0;
393 0 : snap_max = initialize_snapshot_fds( config );
394 0 : snap_retained_max = (ulong)config->firedancer.snapshots.max_full_snapshots_to_keep +
395 0 : (ulong)config->firedancer.snapshots.max_incremental_snapshots_to_keep;
396 0 : snapshot_upload_enabled = fd_topo_find_tile( &config->topo, "snapsv", 0UL )!=ULONG_MAX;
397 0 : snapshot_dio_enabled = fd_topo_find_tile( &config->topo, "snapzp", 0UL )!=ULONG_MAX;
398 :
399 0 : ulong waker_client_cnt = 0UL;
400 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
401 0 : ulong idx = config->topo.tiles[ i ].waker_client_idx;
402 0 : if( FD_UNLIKELY( idx!=ULONG_MAX ) ) waker_client_cnt = fd_ulong_max( waker_client_cnt, idx+1UL );
403 0 : }
404 0 : fd_waker_install( waker_client_cnt );
405 0 : fd_sleep_eventfd_install( &config->topo );
406 :
407 0 : struct spawn_cgroup spawn_cg = {0};
408 :
409 0 : for( ulong pass=0UL; pass<2UL; pass++ ) {
410 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
411 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
412 0 : if( FD_UNLIKELY( (tile->cpu_idx!=ULONG_MAX)!=pass ) ) continue;
413 :
414 0 : if( need_xdp ) {
415 0 : if( FD_UNLIKELY( strcmp( tile->name, "net" ) ) ) {
416 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
417 : /* close XDP related file descriptors */
418 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds[i].xsk_map_fd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
419 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds[i].prog_link_fd, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
420 0 : }
421 0 : } else {
422 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
423 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds[i].xsk_map_fd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
424 0 : if( FD_UNLIKELY( -1==fcntl( xdp_fds[i].prog_link_fd, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
425 0 : }
426 0 : }
427 0 : }
428 :
429 0 : if( need_iavf ) {
430 0 : int const fd_flags = 0!=strcmp( tile->name, "iavf" ) ? FD_CLOEXEC : 0;
431 0 : for( ulong i=0UL; i<iavf_fds.member_cnt; i++ ) {
432 0 : if( FD_UNLIKELY( -1==fcntl( iavf_fds.container_fd[ i ], F_SETFD, fd_flags ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
433 0 : if( FD_UNLIKELY( -1==fcntl( iavf_fds.group_fd [ i ], F_SETFD, fd_flags ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
434 0 : if( FD_UNLIKELY( -1==fcntl( iavf_fds.device_fd [ i ], F_SETFD, fd_flags ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
435 0 : }
436 0 : }
437 :
438 0 : if( need_mlx5 ) {
439 0 : int const fd_flags = strcmp( tile->name, "mlx5" ) ? FD_CLOEXEC : 0;
440 0 : if( FD_UNLIKELY( -1==fcntl( mlx5_fds.cmd_fd, F_SETFD, fd_flags ) ) ) {
441 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
442 0 : }
443 0 : if( FD_UNLIKELY( -1==fcntl( mlx5_fds.async_fd, F_SETFD, fd_flags ) ) ) {
444 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
445 0 : }
446 0 : for( ulong i=0UL; i<mlx5_fds.rx_comp_channel_fd_cnt; i++ ) {
447 0 : int const rx_comp_channel_fd_flags = fd_flags || i!=tile->kind_id ? FD_CLOEXEC : 0;
448 0 : if( FD_UNLIKELY( -1==fcntl( mlx5_fds.rx_comp_channel_fd[ i ],
449 0 : F_SETFD, rx_comp_channel_fd_flags ) ) ) {
450 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
451 0 : }
452 0 : }
453 0 : }
454 :
455 0 : int tile_uses_accdb = 0;
456 0 : int tile_uses_accdb_ro = 0;
457 0 : for( ulong i=0UL; i<tile->uses_obj_cnt; i++ ) {
458 0 : fd_topo_obj_t const * obj = &config->topo.objs[ tile->uses_obj_id[ i ] ];
459 0 : if( FD_UNLIKELY( !strcmp( obj->name, "accdb" ) ) ) {
460 0 : if( FD_UNLIKELY( tile->uses_obj_mode[ i ]==FD_SHMEM_JOIN_MODE_READ_ONLY ) ) tile_uses_accdb_ro = 1;
461 0 : else tile_uses_accdb = 1;
462 0 : break;
463 0 : }
464 0 : }
465 :
466 : /* The gui joins the accdb shmem read-only (for partition stats)
467 : but never reads account data from the on-disk file, so it does
468 : not need the accounts.db fd. Withhold it to keep the gui at
469 : least privilege. */
470 0 : if( FD_UNLIKELY( !strcmp( tile->name, "gui" ) ) ) tile_uses_accdb_ro = 0;
471 0 : if( FD_UNLIKELY( !strcmp( tile->name, "snapmk" ) ) ) { tile_uses_accdb = 0; tile_uses_accdb_ro = 1; }
472 :
473 0 : if( FD_UNLIKELY( tile_uses_accdb ) ) {
474 0 : if( FD_UNLIKELY( -1==fcntl( FD_ACCDB_FD_RW, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
475 0 : } else {
476 0 : if( FD_UNLIKELY( -1==fcntl( FD_ACCDB_FD_RW, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
477 0 : }
478 :
479 0 : if( FD_UNLIKELY( tile_uses_accdb_ro ) ) {
480 0 : if( FD_UNLIKELY( -1==fcntl( FD_ACCDB_FD_RO, F_SETFD, 0 ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
481 0 : } else {
482 0 : if( FD_UNLIKELY( -1==fcntl( FD_ACCDB_FD_RO, F_SETFD, FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD,FD_CLOEXEC) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
483 0 : }
484 :
485 0 : ulong txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX );
486 0 : int tile_uses_txncache = 0;
487 0 : for( ulong i=0UL; i<tile->uses_obj_cnt; i++ ) tile_uses_txncache |= tile->uses_obj_id[ i ]==txncache_obj_id;
488 0 : if( FD_UNLIKELY( fcntl( FD_TXNCACHE_FD, F_SETFD, tile_uses_txncache ? 0 : FD_CLOEXEC )<0 ) )
489 0 : FD_LOG_ERR(( "fcntl(FD_TXNCACHE_FD,F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
490 :
491 0 : if( FD_LIKELY( has_store ) ) {
492 0 : int tile_uses_store = 0;
493 0 : for( ulong i=0UL; i<tile->uses_obj_cnt; i++ ) tile_uses_store |= tile->uses_obj_id[ i ]==store_obj_id;
494 0 : int tile_uses_store_rw = tile_uses_store &&
495 0 : (!strcmp( tile->name, "shred" ) ||
496 0 : !strcmp( tile->name, "backt" ) ||
497 0 : !strcmp( tile->name, "rserve" ));
498 0 : int tile_uses_store_ro = tile_uses_store && !strcmp( tile->name, "replay" );
499 0 : if( FD_UNLIKELY( fcntl( FD_STORE_FD_RW, F_SETFD, tile_uses_store_rw ? 0 : FD_CLOEXEC )<0 ) )
500 0 : FD_LOG_ERR(( "fcntl(FD_STORE_FD_RW,F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
501 0 : if( FD_UNLIKELY( fcntl( FD_STORE_FD_RO, F_SETFD, tile_uses_store_ro ? 0 : FD_CLOEXEC )<0 ) )
502 0 : FD_LOG_ERR(( "fcntl(FD_STORE_FD_RO,F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
503 0 : }
504 :
505 0 : if( FD_UNLIKELY( -1==fcntl( FD_EPOCH_CREDITS_FD, F_SETFD, !strcmp( tile->name, "replay" ) ? 0 : FD_CLOEXEC ) ) )
506 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
507 :
508 0 : if( FD_UNLIKELY( -1==fcntl( FD_COST_TRACKER_FD, F_SETFD, !strcmp( tile->name, "replay" ) ? 0 : FD_CLOEXEC ) ) )
509 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
510 :
511 0 : int tile_uses_vote_stakes = !strcmp( tile->name, "replay" ) || !strcmp( tile->name, "execle" ) ||
512 0 : !strcmp( tile->name, "execrp" ) || !strcmp( tile->name, "snapmk" );
513 0 : if( FD_UNLIKELY( -1==fcntl( FD_VOTE_STAKES_FD, F_SETFD, tile_uses_vote_stakes ? 0 : FD_CLOEXEC ) ) )
514 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
515 :
516 0 : int tile_uses_stake_spill = !strcmp( tile->name, "replay" ) || !strcmp( tile->name, "execle" ) ||
517 0 : !strcmp( tile->name, "execrp" ) || !strcmp( tile->name, "snapin" );
518 0 : if( FD_UNLIKELY( -1==fcntl( FD_STAKE_DELEGATIONS_FD, F_SETFD, tile_uses_stake_spill ? 0 : FD_CLOEXEC ) ) )
519 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
520 :
521 0 : int tile_uses_snap_fd = !strcmp( tile->name, "snapmk" );
522 0 : int tile_uses_snap_dio_fd = !strcmp( tile->name, "snapzp" );
523 0 : int tile_uses_snap_rd_fd = !strcmp( tile->name, "snapsv" );
524 0 : int tile_uses_snap_all_fd = !strcmp( tile->name, "snapct" );
525 0 : for( ulong j=0UL; j<snap_max; j++ ) {
526 0 : int retained = j<snap_retained_max;
527 0 : if( FD_UNLIKELY( -1==fcntl( FD_SNAP_FD( j ), F_SETFD, tile_uses_snap_all_fd || ( tile_uses_snap_fd && retained ) ? 0 : FD_CLOEXEC ) ) )
528 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
529 0 : if( snapshot_dio_enabled ) {
530 0 : if( FD_UNLIKELY( -1==fcntl( FD_SNAP_DIO_FD( j ), F_SETFD, tile_uses_snap_dio_fd && retained ? 0 : FD_CLOEXEC ) ) )
531 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
532 0 : }
533 0 : if( snapshot_upload_enabled ) {
534 0 : if( FD_UNLIKELY( -1==fcntl( FD_SNAP_RO_FD( j ), F_SETFD, tile_uses_snap_rd_fd && retained ? 0 : FD_CLOEXEC ) ) )
535 0 : FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
536 0 : }
537 0 : }
538 :
539 0 : int is_waker = !strcmp( tile->name, "waker" );
540 0 : int outer_entitled = is_waker || tile->waker_client_idx!=ULONG_MAX;
541 0 : if( FD_UNLIKELY( -1==fcntl( FD_WAKER_OUTER_FD, F_SETFD, outer_entitled ? 0 : FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
542 0 : for( ulong j=0UL; j<waker_client_cnt; j++ ) {
543 0 : int inner_entitled = is_waker || tile->waker_client_idx==j;
544 0 : if( FD_UNLIKELY( -1==fcntl( FD_WAKER_INNER_FD( j ), F_SETFD, inner_entitled ? 0 : FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
545 0 : }
546 :
547 0 : int is_mwaitx = !strcmp( tile->name, "mwaitx" );
548 0 : for( ulong j=0UL; j<config->topo.tile_cnt; j++ ) {
549 0 : if( FD_LIKELY( !config->topo.tiles[ j ].sleep_eventfd ) ) continue;
550 :
551 0 : int eventfd_entitled = is_mwaitx || tile->id==j;
552 0 : if( FD_UNLIKELY( -1==fcntl( FD_SLEEP_EVENTFD( j ), F_SETFD, eventfd_entitled ? 0 : FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
553 0 : }
554 :
555 0 : int pipefd[ 2 ];
556 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
557 0 : fds[ child_cnt ] = (struct pollfd){ .fd = pipefd[ 0 ], .events = 0 };
558 :
559 0 : int floating_priority = ( any_floats && !strcmp( tile->name, "waker" ) ) ? -19 : save_priority;
560 0 : child_pids[ child_cnt ] = execve_tile( config->name, tile, float_cpu_set, cpuless_cpu_set, floating_priority, config_memfd, pipefd[ 1 ], &spawn_cg );
561 0 : child_idxs[ child_cnt ] = i;
562 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
563 0 : strncpy( child_names[ child_cnt ], tile->name, 32 );
564 0 : child_cnt++;
565 0 : }
566 0 : }
567 :
568 0 : leave_isolation_cgroup( &spawn_cg );
569 :
570 : /* Obtain the actual grandchild PID from the pipe */
571 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
572 0 : if( FD_UNLIKELY( actual_pids[ i ]!=ULONG_MAX ) ) continue;
573 0 : FD_TEST( 8UL==read( fds[ i ].fd, &actual_pids[ i ], 8UL ) );
574 0 : }
575 :
576 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, save_priority ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
577 0 : if( FD_UNLIKELY( fd_cpuset_setaffinity( 0, floating_cpu_set ) ) )
578 0 : FD_LOG_ERR(( "fd_cpuset_setaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
579 :
580 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
581 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
582 0 : if( strcmp( tile->name, "sign" ) || tile->sign.failover_junk_fd==-1 ) continue;
583 0 : if( FD_UNLIKELY( close( tile->sign.failover_junk_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
584 0 : }
585 :
586 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
587 0 : if( need_xdp ) {
588 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
589 0 : if( FD_UNLIKELY( close( xdp_fds[i].xsk_map_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
590 0 : if( FD_UNLIKELY( close( xdp_fds[i].prog_link_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
591 0 : }
592 0 : }
593 :
594 0 : if( FD_UNLIKELY( -1==close( FD_ACCDB_FD_RW ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
595 0 : if( FD_UNLIKELY( -1==close( FD_ACCDB_FD_RO ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
596 0 : if( FD_UNLIKELY( -1==close( FD_TXNCACHE_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
597 0 : if( FD_LIKELY( has_store ) ) {
598 0 : if( FD_UNLIKELY( -1==close( FD_STORE_FD_RW ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
599 0 : if( FD_UNLIKELY( -1==close( FD_STORE_FD_RO ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
600 0 : }
601 0 : if( FD_UNLIKELY( -1==close( FD_EPOCH_CREDITS_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
602 0 : if( FD_UNLIKELY( -1==close( FD_COST_TRACKER_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
603 0 : if( FD_UNLIKELY( -1==close( FD_VOTE_STAKES_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
604 0 : if( FD_UNLIKELY( -1==close( FD_STAKE_DELEGATIONS_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
605 0 : for( ulong j=0UL; j<snap_max; j++ ) {
606 0 : if( FD_UNLIKELY( -1==close( FD_SNAP_FD( j ) ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
607 0 : if( snapshot_dio_enabled )
608 0 : if( FD_UNLIKELY( -1==close( FD_SNAP_DIO_FD( j ) ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
609 0 : if( snapshot_upload_enabled )
610 0 : if( FD_UNLIKELY( -1==close( FD_SNAP_RO_FD( j ) ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
611 0 : }
612 :
613 0 : if( FD_UNLIKELY( -1==close( FD_WAKER_OUTER_FD ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
614 0 : for( ulong j=0UL; j<waker_client_cnt; j++ ) {
615 0 : if( FD_UNLIKELY( -1==close( FD_WAKER_INNER_FD( j ) ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
616 0 : }
617 0 : for( ulong j=0UL; j<config->topo.tile_cnt; j++ ) {
618 0 : if( FD_LIKELY( !config->topo.tiles[ j ].sleep_eventfd ) ) continue;
619 0 : if( FD_UNLIKELY( -1==close( FD_SLEEP_EVENTFD( j ) ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
620 0 : }
621 :
622 0 : int allow_fds[ 4+FD_TOPO_MAX_TILES ];
623 0 : ulong allow_fds_cnt = 0;
624 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
625 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
626 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
627 0 : allow_fds[ allow_fds_cnt++ ] = args->pipefd[ 1 ]; /* write end of main pipe */
628 0 : for( ulong i=0UL; i<child_cnt; i++ )
629 0 : allow_fds[ allow_fds_cnt++ ] = fds[ i ].fd; /* read end of child pipes */
630 0 : if( need_iavf ) {
631 0 : for( ulong i=0UL; i<iavf_fds.member_cnt; i++ ) {
632 0 : if( FD_UNLIKELY( -1==close( iavf_fds.container_fd[ i ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
633 0 : if( FD_UNLIKELY( -1==close( iavf_fds.group_fd [ i ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
634 0 : if( FD_UNLIKELY( -1==close( iavf_fds.device_fd [ i ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
635 0 : }
636 0 : }
637 0 : if( need_mlx5 ) {
638 0 : if( FD_UNLIKELY( -1==close( mlx5_fds.cmd_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
639 0 : if( FD_UNLIKELY( -1==close( mlx5_fds.async_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
640 0 : for( ulong i=0UL; i<mlx5_fds.rx_comp_channel_fd_cnt; i++ ) {
641 0 : if( FD_UNLIKELY( -1==close( mlx5_fds.rx_comp_channel_fd[ i ] ) ) )
642 0 : FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
643 0 : }
644 0 : }
645 :
646 0 : struct sock_filter seccomp_filter[ 128UL ];
647 0 : unsigned int instr_cnt;
648 : #if defined(__aarch64__) || defined(__riscv)
649 : populate_sock_filter_policy_pidns_no_poll( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd() );
650 : instr_cnt = sock_filter_policy_pidns_no_poll_instr_cnt;
651 : #else
652 0 : populate_sock_filter_policy_pidns( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd() );
653 0 : instr_cnt = sock_filter_policy_pidns_instr_cnt;
654 0 : #endif
655 :
656 0 : if( FD_LIKELY( config->development.sandbox ) ) {
657 0 : fd_sandbox_enter( config->uid,
658 0 : config->gid,
659 0 : 0,
660 0 : 0,
661 0 : 0,
662 0 : 0,
663 0 : 0,
664 0 : 1UL+child_cnt, /* RLIMIT_NOFILE needs to be set to the nfds argument of poll() */
665 0 : 0UL,
666 0 : 0UL,
667 0 : 0UL,
668 0 : allow_fds_cnt,
669 0 : allow_fds,
670 0 : instr_cnt,
671 0 : seccomp_filter );
672 0 : } else {
673 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
674 0 : }
675 :
676 : /* Reap child process PIDs so they don't show up in `ps` etc. All of
677 : these children should have exited immediately after clone(2)'ing
678 : another child with a huge page based stack. */
679 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
680 0 : int wstatus;
681 0 : int exited_pid = wait4( child_pids[ i ], &wstatus, (int)__WALL, NULL );
682 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
683 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s) %lu %hu", errno, fd_io_strerror( errno ), i, fds[i].revents ));
684 0 : } else if( FD_UNLIKELY( child_pids[ i ]!=exited_pid ) ) {
685 0 : FD_LOG_ERR(( "pidns wait4() returned unexpected pid %d %d", child_pids[ i ], exited_pid ));
686 0 : } else if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
687 0 : FD_LOG_ERR_NOEXIT(( "tile %lu (%s) exited while booting with signal %d (%s)", i, child_names[ i ], WTERMSIG( wstatus ), fd_io_strsignal( WTERMSIG( wstatus ) ) ));
688 0 : fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
689 0 : }
690 0 : if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) ) {
691 0 : FD_LOG_ERR_NOEXIT(( "tile %lu (%s) exited while booting with code %d", i, child_names[ i ], WEXITSTATUS( wstatus ) ));
692 0 : fd_sys_util_exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
693 0 : }
694 0 : }
695 :
696 0 : fds[ child_cnt ] = (struct pollfd){ .fd = args->pipefd[ 1 ], .events = 0 };
697 0 : strncpy( child_names[ child_cnt ], "parent", 32UL );
698 0 : child_idxs[ child_cnt ] = ULONG_MAX;
699 :
700 : /* We are now the init process of the pid namespace. If the init
701 : process dies, all children are terminated. If any child dies, we
702 : terminate the init process, which will cause the kernel to
703 : terminate all other children bringing all of our processes down as
704 : a group. The parent process will also die if this process dies,
705 : due to getting SIGHUP on the pipe. */
706 0 : while( 1 ) {
707 0 : if( FD_UNLIKELY( -1==poll( fds, 1UL+child_cnt, (int)-1 ) ) ) FD_LOG_ERR(( "poll() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
708 :
709 : /* Parent process died, probably SIGINT, exit gracefully. */
710 0 : if( FD_UNLIKELY( fds[ child_cnt ].revents ) ) fd_sys_util_exit_group( 0 );
711 :
712 : /* Child process died, reap it to figure out exit code. */
713 0 : int wstatus;
714 0 : int exited_pid = wait4( -1, &wstatus, (int)__WALL | (int)WNOHANG, NULL );
715 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
716 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
717 0 : } else if( FD_UNLIKELY( !exited_pid ) ) {
718 : /* Spurious wakeup, no child actually dead yet. */
719 0 : continue;
720 0 : }
721 :
722 : /* Now find the tile corresponding to that PID */
723 0 : FD_TEST( exited_pid>0 );
724 0 : int found = 0;
725 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
726 0 : if( FD_LIKELY( actual_pids[ i ]!=(ulong)exited_pid ) ) continue;
727 :
728 0 : found = 1;
729 0 : fds[ i ].fd = -1; /* Don't poll on this tile anymore */
730 :
731 0 : char * tile_name = child_names[ i ];
732 0 : ulong tile_idx = child_idxs[ i ];
733 0 : ulong tile_id = config->topo.tiles[ tile_idx ].kind_id;
734 :
735 0 : if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
736 0 : FD_LOG_ERR_NOEXIT(( "tile %s%s:%lu%s exited with signal %d %s(%s)%s", fd_log_style_bold(), tile_name, tile_id, fd_log_style_normal(), WTERMSIG( wstatus ), fd_log_style_dim(), fd_io_strsignal( WTERMSIG( wstatus ) ), fd_log_style_normal() ));
737 0 : fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
738 0 : } else {
739 0 : int exit_code = WEXITSTATUS( wstatus );
740 0 : if( FD_LIKELY( !exit_code && tile_idx!=ULONG_MAX && config->topo.tiles[ tile_idx ].allow_shutdown ) ) {
741 0 : found = 1;
742 0 : FD_LOG_INFO(( "tile %s:%lu exited gracefully with code %d", tile_name, tile_id, exit_code ));
743 0 : } else {
744 0 : FD_LOG_ERR_NOEXIT(( "tile %s%s:%lu%s exited with code %d", fd_log_style_bold(), tile_name, tile_id, fd_log_style_normal(), exit_code ));
745 0 : fd_sys_util_exit_group( exit_code ? exit_code : 1 );
746 0 : }
747 0 : }
748 0 : }
749 :
750 0 : if( FD_UNLIKELY( !found ) ) FD_LOG_ERR(( "wait4() returned unexpected pid %d", exited_pid ));
751 0 : }
752 :
753 0 : return 0;
754 0 : }
755 :
756 : int
757 : clone_firedancer( config_t const * config,
758 : int close_fd,
759 0 : int * out_pipe ) {
760 : /* This pipe is here just so that the child process knows when the
761 : parent has died (it will get a HUP). */
762 0 : int pipefd[2];
763 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC | O_NONBLOCK ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
764 :
765 : /* clone into a pid namespace */
766 0 : int flags = config->development.sandbox ? CLONE_NEWPID : 0;
767 0 : struct pidns_clone_args args = { .config = config, .closefd = close_fd, .pipefd = pipefd, };
768 :
769 0 : void * stack = create_clone_stack();
770 :
771 0 : int pid_namespace = clone( main_pid_namespace, (uchar *)stack + FD_TILE_PRIVATE_STACK_SZ, flags, &args );
772 0 : if( FD_UNLIKELY( pid_namespace<0 ) ) FD_LOG_ERR(( "clone() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
773 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
774 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
775 0 : if( strcmp( tile->name, "sign" ) || tile->sign.failover_junk_fd==-1 ) continue;
776 0 : if( FD_UNLIKELY( close( tile->sign.failover_junk_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
777 0 : }
778 :
779 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
780 :
781 0 : *out_pipe = pipefd[ 0 ];
782 0 : return pid_namespace;
783 0 : }
784 :
785 : static void
786 : workspace_path( config_t const * config,
787 : fd_topo_wksp_t const * wksp,
788 0 : char out[ PATH_MAX ] ) {
789 0 : char const * mount_path;
790 0 : switch( wksp->page_sz ) {
791 0 : case FD_SHMEM_HUGE_PAGE_SZ:
792 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
793 0 : break;
794 0 : case FD_SHMEM_GIGANTIC_PAGE_SZ:
795 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
796 0 : break;
797 0 : case FD_SHMEM_NORMAL_PAGE_SZ:
798 0 : mount_path = config->hugetlbfs.normal_page_mount_path;
799 0 : break;
800 0 : default:
801 0 : FD_LOG_ERR(( "invalid page size %lu", wksp->page_sz ));
802 0 : }
803 :
804 0 : FD_TEST( fd_cstr_printf_check( out, PATH_MAX, NULL, "%s/%s_%s.wksp", mount_path, config->name, wksp->name ) );
805 0 : }
806 :
807 : static void
808 : warn_unknown_files( config_t const * config,
809 0 : ulong mount_type ) {
810 0 : char const * mount_path;
811 0 : switch( mount_type ) {
812 0 : case 0UL:
813 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
814 0 : break;
815 0 : case 1UL:
816 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
817 0 : break;
818 0 : default:
819 0 : FD_LOG_ERR(( "invalid mount type %lu", mount_type ));
820 0 : }
821 :
822 : /* Check if there are any files in mount_path */
823 0 : DIR * dir = opendir( mount_path );
824 0 : if( FD_UNLIKELY( !dir ) ) {
825 0 : if( FD_UNLIKELY( errno!=ENOENT ) ) FD_LOG_ERR(( "error opening `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
826 0 : return;
827 0 : }
828 :
829 0 : struct dirent * entry;
830 0 : for(;;) {
831 0 : errno = 0;
832 0 : entry = readdir( dir );
833 0 : if( FD_UNLIKELY( !entry ) ) break;
834 0 : if( FD_UNLIKELY( !strcmp( entry->d_name, ".") || !strcmp( entry->d_name, ".." ) ) ) continue;
835 :
836 0 : char entry_path[ PATH_MAX ];
837 0 : FD_TEST( fd_cstr_printf_check( entry_path, PATH_MAX, NULL, "%s/%s", mount_path, entry->d_name ));
838 :
839 0 : int known_file = 0;
840 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
841 0 : fd_topo_wksp_t const * wksp = &config->topo.workspaces[ i ];
842 :
843 0 : char expected_path[ PATH_MAX ];
844 0 : workspace_path( config, wksp, expected_path );
845 :
846 0 : if( !strcmp( entry_path, expected_path ) ) {
847 0 : known_file = 1;
848 0 : break;
849 0 : }
850 0 : }
851 :
852 0 : if( mount_type==0UL ) {
853 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
854 0 : fd_topo_tile_t const * tile = &config->topo.tiles [ i ];
855 :
856 0 : char expected_path[ PATH_MAX ];
857 0 : FD_TEST( fd_cstr_printf_check( expected_path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
858 :
859 0 : if( !strcmp( entry_path, expected_path ) ) {
860 0 : known_file = 1;
861 0 : break;
862 0 : }
863 0 : }
864 0 : }
865 :
866 0 : if( FD_UNLIKELY( !known_file ) ) FD_LOG_WARNING(( "unknown file `%s` found in `%s`", entry->d_name, mount_path ));
867 0 : }
868 :
869 0 : if( FD_UNLIKELY( errno ) ) FD_LOG_ERR(( "error reading dir `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
870 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "error closing `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
871 0 : }
872 :
873 : void
874 0 : initialize_workspaces( config_t * config ) {
875 : /* Switch to non-root uid/gid for workspace creation. Permissions
876 : checks are still done as the current user. */
877 0 : uint gid = getgid();
878 0 : uint uid = getuid();
879 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
880 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
881 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
882 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
883 :
884 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
885 0 : fd_topo_wksp_t * wksp = &config->topo.workspaces[ i ];
886 :
887 0 : char path[ PATH_MAX ];
888 0 : workspace_path( config, wksp, path );
889 :
890 0 : struct stat st;
891 0 : int result = stat( path, &st );
892 :
893 0 : int update_existing;
894 0 : if( FD_UNLIKELY( !result && config->is_live_cluster && !config->is_dev ) ) {
895 0 : if( FD_UNLIKELY( -1==unlink( path ) && errno!=ENOENT ) ) FD_LOG_ERR(( "unlink() failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
896 0 : update_existing = 0;
897 0 : } else if( FD_UNLIKELY( !result ) ) {
898 : /* Creating all of the workspaces is very expensive because the
899 : kernel has to zero out all of the pages. There can be tens or
900 : hundreds of gigabytes of zeroing to do.
901 :
902 : What would be really nice is if the kernel let us create huge
903 : pages without zeroing them, but it's not possible. The
904 : ftruncate and fallocate calls do not support this type of
905 : resize with the hugetlbfs filesystem.
906 :
907 : Instead.. to prevent repeatedly doing this zeroing every time
908 : we start the validator, we have a small hack here to re-use the
909 : workspace files if they exist. */
910 0 : update_existing = 1;
911 0 : } else if( FD_LIKELY( result && errno==ENOENT ) ) {
912 0 : update_existing = 0;
913 0 : } else {
914 0 : FD_LOG_ERR(( "stat failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
915 0 : }
916 :
917 0 : if( FD_UNLIKELY( -1==fd_topo_create_workspace( &config->topo, wksp, update_existing ) ) ) {
918 0 : FD_TEST( errno==ENOMEM );
919 :
920 0 : warn_unknown_files( config, wksp->page_sz!=FD_SHMEM_HUGE_PAGE_SZ );
921 :
922 0 : char path[ PATH_MAX ];
923 0 : workspace_path( config, wksp, path );
924 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create workspace `%s` at `%s` "
925 0 : "with %lu %s pages. Firedancer reserves enough memory for all of its workspaces "
926 0 : "during the `hugetlbfs` configure step, so it is likely you have unknown files "
927 0 : "left over in this directory which are consuming memory, or another program on "
928 0 : "the system is using pages from the same mount.",
929 0 : wksp->name, path, wksp->page_cnt, fd_shmem_page_sz_to_cstr( wksp->page_sz ) ));
930 0 : }
931 0 : fd_topo_join_workspace( &config->topo, wksp, FD_SHMEM_JOIN_MODE_READ_WRITE, 0 );
932 0 : fd_topo_wksp_new( &config->topo, wksp, CALLBACKS );
933 0 : fd_topo_leave_workspace( &config->topo, wksp );
934 0 : }
935 :
936 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
937 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
938 0 : }
939 :
940 : void
941 0 : initialize_stacks( config_t const * config ) {
942 : # if FD_HAS_MSAN
943 : /* MSan calls an external symbolizer using fork() on crashes, which is
944 : incompatible with Firedancer's MAP_SHARED stacks. */
945 : (void)config;
946 : return;
947 : # endif
948 :
949 : /* Switch to non-root uid/gid for workspace creation. Permissions
950 : checks are still done as the current user. */
951 0 : uint gid = getgid();
952 0 : uint uid = getuid();
953 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
954 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
955 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
956 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
957 :
958 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
959 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
960 :
961 0 : char path[ PATH_MAX ];
962 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
963 :
964 0 : struct stat st;
965 0 : int result = stat( path, &st );
966 :
967 0 : int update_existing;
968 0 : if( FD_UNLIKELY( !result && config->is_live_cluster ) ) {
969 0 : if( FD_UNLIKELY( -1==unlink( path ) && errno!=ENOENT ) ) FD_LOG_ERR(( "unlink() failed when trying to create stack workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
970 0 : update_existing = 0;
971 0 : } else if( FD_UNLIKELY( !result ) ) {
972 : /* See above note about zeroing out pages. */
973 0 : update_existing = 1;
974 0 : } else if( FD_LIKELY( result && errno==ENOENT ) ) {
975 0 : update_existing = 0;
976 0 : } else {
977 0 : FD_LOG_ERR(( "stat failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
978 0 : }
979 :
980 : /* TODO: Use a better CPU idx for the stack if tile is floating */
981 0 : ulong stack_cpu_idx = 0UL;
982 0 : if( FD_LIKELY( tile->cpu_idx<65535UL ) ) stack_cpu_idx = tile->cpu_idx;
983 :
984 0 : char name[ PATH_MAX ];
985 0 : FD_TEST( fd_cstr_printf_check( name, PATH_MAX, NULL, "%s_stack_%s%lu", config->name, tile->name, tile->kind_id ) );
986 :
987 0 : ulong sub_page_cnt[ 1 ] = { 6 };
988 0 : ulong sub_cpu_idx [ 1 ] = { stack_cpu_idx };
989 0 : int err;
990 0 : if( FD_UNLIKELY( update_existing ) ) {
991 0 : err = fd_shmem_update_multi( name, FD_SHMEM_HUGE_PAGE_SZ, 1, sub_page_cnt, sub_cpu_idx, S_IRUSR | S_IWUSR ); /* logs details */
992 0 : } else {
993 0 : err = fd_shmem_create_multi( name, FD_SHMEM_HUGE_PAGE_SZ, 1, sub_page_cnt, sub_cpu_idx, S_IRUSR | S_IWUSR ); /* logs details */
994 0 : }
995 0 : if( FD_UNLIKELY( err && errno==ENOMEM ) ) {
996 0 : warn_unknown_files( config, 0UL );
997 :
998 0 : char path[ PATH_MAX ];
999 0 : FD_TEST( fd_cstr_printf_check( path, PATH_MAX, NULL, "%s/%s_stack_%s%lu", config->hugetlbfs.huge_page_mount_path, config->name, tile->name, tile->kind_id ) );
1000 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create huge page stack for tile `%s` at `%s`. "
1001 0 : "Firedancer reserves enough memory for all of its stacks during the `hugetlbfs` configure "
1002 0 : "step, so it is likely you have unknown files left over in this directory which are "
1003 0 : "consuming memory, or another program on the system is using pages from the same mount.",
1004 0 : tile->name, path ));
1005 0 : } else if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_shmem_create_multi failed" ));
1006 0 : }
1007 :
1008 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1009 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1010 0 : }
1011 :
1012 : void
1013 0 : fdctl_check_configure( config_t const * config ) {
1014 0 : # if defined(__x86_64__)
1015 0 : uint eax, ebx, ecx, edx;
1016 0 : if( FD_LIKELY( __get_cpuid_count( 7U, 0U, &eax, &ebx, &ecx, &edx ) ) ) {
1017 0 : int cpu_avx512 = ((ebx>>16)&1U) && ((ebx>>21)&1U); /* avx512f, avx512ifma */
1018 0 : int cpu_gfni = (int)((ecx>>8)&1U);
1019 0 : if( FD_UNLIKELY( (cpu_avx512 && !FD_HAS_AVX512) || (cpu_gfni && !FD_HAS_GFNI) ) )
1020 0 : FD_LOG_WARNING(( "This CPU supports%s%s but the binary was built without them, so it runs slower code paths. "
1021 0 : "Rebuild on this host with MACHINE=native, or with a MACHINE matching this CPU.",
1022 0 : (cpu_avx512 && !FD_HAS_AVX512) ? " AVX-512" : "", (cpu_gfni && !FD_HAS_GFNI) ? " GFNI" : "" ));
1023 0 : }
1024 0 : # endif
1025 :
1026 0 : configure_result_t check = fd_cfg_stage_hugetlbfs.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1027 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1028 0 : FD_LOG_ERR(( "Huge pages are not configured correctly: %s. You can run `%s configure init hugetlbfs` "
1029 0 : "to create the mounts correctly. This must be done after every system restart before running "
1030 0 : "Firedancer.", check.message, FD_BINARY_NAME ));
1031 :
1032 0 : if( FD_LIKELY( 0==strcmp( config->net.provider, "xdp" ) ) ) {
1033 0 : if( fd_cfg_stage_bonding.enabled( config ) ) {
1034 0 : check = fd_cfg_stage_bonding.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1035 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1036 0 : FD_LOG_ERR(( "Bonded network device is not configured correctly: %s. You can run `%s configure init bonding` "
1037 0 : "to configure the bonding driver.", check.message, FD_BINARY_NAME ));
1038 0 : }
1039 :
1040 0 : check = fd_cfg_stage_ethtool_channels.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1041 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1042 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-channels` to set the number of channels on the "
1043 0 : "network device correctly.", check.message, FD_BINARY_NAME ));
1044 :
1045 0 : check = fd_cfg_stage_ethtool_offloads.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1046 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1047 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-offloads` to disable features "
1048 0 : "as required.", check.message, FD_BINARY_NAME ));
1049 :
1050 0 : check = fd_cfg_stage_ethtool_loopback.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1051 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1052 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-loopback` to disable tx-udp-segmentation "
1053 0 : "on the loopback device.", check.message, FD_BINARY_NAME ));
1054 0 : }
1055 :
1056 0 : if( FD_LIKELY( 0==strcmp( config->net.provider, "mlx5" ) ) ) {
1057 0 : check = fd_cfg_stage_mlx5.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1058 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1059 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init mlx5` to configure mlx5 network devices and load the ib_uverbs kernel module.",
1060 0 : check.message, FD_BINARY_NAME ));
1061 0 : }
1062 :
1063 0 : if( FD_LIKELY( 0==strcmp( config->net.provider, "iavf" ) ) ) {
1064 0 : check = fd_cfg_stage_iavf.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1065 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1066 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init iavf` to configure the VFs and PF filters and load vfio-pci.",
1067 0 : check.message, FD_BINARY_NAME ));
1068 0 : }
1069 :
1070 0 : check = fd_cfg_stage_sysctl.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1071 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1072 0 : FD_LOG_ERR(( "Kernel parameters are not configured correctly: %s. You can run `%s configure init sysctl` "
1073 0 : "to set kernel parameters correctly.", check.message, FD_BINARY_NAME ));
1074 :
1075 : /* hyperthreads, nohz-full and rcu-nocbs are check-only stages: they
1076 : emit warnings themselves and always return OK, so there is no
1077 : result to act on (and no init to point the operator at). */
1078 0 : (void)fd_cfg_stage_hyperthreads.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1079 0 : (void)fd_cfg_stage_nohz_full.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1080 0 : (void)fd_cfg_stage_rcu_nocbs.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1081 :
1082 : /* kworkers and cpuset are optional (but recommended) hardening: an
1083 : unconfigured stage only warns. A PARTIALLY_CONFIGURED cpuset is
1084 : fatal however: the isolation cgroup exists but covers the wrong
1085 : CPUs (e.g. stale from a previous [layout.affinity]), and the tile
1086 : launcher would join it and then fail to pin with a confusing
1087 : EINVAL. Fail up front with the fix instead. */
1088 0 : if( FD_UNLIKELY( fd_cfg_stage_kworkers.enabled( config ) ) ) {
1089 0 : check = fd_cfg_stage_kworkers.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1090 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1091 0 : FD_LOG_WARNING(( "Kernel workqueues may steal CPU time from Firedancer tiles: %s. For lower jitter, run "
1092 0 : "`%s configure init kworkers`.", check.message, FD_BINARY_NAME ));
1093 0 : }
1094 :
1095 : /* Floating tiles need a scheduler domain to be balanced across their
1096 : CPUs; isolcpus= removes it, and every floater would stay on the
1097 : CPU it was forked on. */
1098 0 : FD_CPUSET_DECL( isolated );
1099 0 : if( FD_LIKELY( fd_cpu_isolation_read_list( "/sys/devices/system/cpu/isolated", isolated ) ) ) {
1100 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
1101 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
1102 0 : if( FD_UNLIKELY( tile->floats && fd_cpuset_test( isolated, tile->cpu_idx ) ) )
1103 0 : FD_LOG_ERR(( "tile %s:%lu floats on CPU %lu, which the isolcpus= boot parameter removed from the kernel scheduler. "
1104 0 : "Floating tiles need their CPUs scheduled: drop them from isolcpus= and isolate them with "
1105 0 : "`%s configure init cpuset` instead.", tile->name, tile->kind_id, tile->cpu_idx, FD_BINARY_NAME ));
1106 0 : }
1107 0 : }
1108 :
1109 0 : FD_CPUSET_DECL( partition );
1110 0 : fd_cpu_isolation_partition_cpus( partition, &config->topo );
1111 0 : FD_CPUSET_DECL( housekeeping );
1112 0 : fd_cpuset_subtract( housekeeping, fd_cpu_isolation_online_cpus( housekeeping ), partition );
1113 0 : if( FD_UNLIKELY( fd_cpuset_cnt( partition ) && fd_cpuset_cnt( housekeeping )<2UL ) ) {
1114 0 : char list[ FD_CPU_ISOLATION_LIST_MAX ];
1115 0 : FD_LOG_WARNING(( "only %lu online CPU(s) (%s) remain outside Firedancer tile CPUs and their isolated hyperthread "
1116 0 : "siblings for the kernel, interrupts and other processes. Consider reducing tile counts or "
1117 0 : "adding to [layout.blocklist_cores].", fd_cpuset_cnt( housekeeping ),
1118 0 : fd_cpu_isolation_format_list( list, sizeof(list), housekeeping ) ));
1119 0 : }
1120 :
1121 0 : if( FD_LIKELY( fd_cfg_stage_cpuset.enabled( config ) ) ) {
1122 0 : check = fd_cfg_stage_cpuset.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
1123 0 : if( FD_UNLIKELY( check.result==CONFIGURE_PARTIALLY_CONFIGURED ) )
1124 0 : FD_LOG_ERR(( "The CPU isolation cgroup exists but does not match the topology: %s. Tiles would fail to pin "
1125 0 : "to their CPUs. Run `%s configure init cpuset` to fix it, or `%s configure fini cpuset` to "
1126 0 : "remove it.", check.message, FD_BINARY_NAME, FD_BINARY_NAME ));
1127 0 : else if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
1128 0 : FD_LOG_WARNING(( "Firedancer tile CPUs are not isolated from other processes: %s. For lower jitter, run "
1129 0 : "`%s configure init cpuset`.", check.message, FD_BINARY_NAME ));
1130 0 : }
1131 :
1132 0 : fd_cpu_isolation_check_cgroup( config );
1133 0 : }
1134 :
1135 : /* Each Sign tile gets its own one-shot pipe for receiving the ephemeral
1136 : generated junk key. */
1137 : static void FD_FN_SENSITIVE
1138 0 : failover_junk_prepare( config_t * config ) {
1139 0 : if( fd_topo_find_tile( &config->topo, "failov", 0UL )==ULONG_MAX ) return;
1140 0 : uchar const * key = fd_keyload_generate();
1141 0 : fd_memcpy( config->topo.failover_junk, key+32UL, 32UL );
1142 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
1143 0 : fd_topo_tile_t * tile = &config->topo.tiles[ i ];
1144 0 : if( strcmp( tile->name, "sign" ) ) continue;
1145 0 : FD_TEST( tile->sign.failover_enabled && tile->sign.failover_junk_fd==-1 );
1146 0 : int pipefd[ 2 ];
1147 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1148 0 : for( ulong off=0UL; off<64UL; ) {
1149 0 : long n = write( pipefd[ 1 ], key+off, 64UL-off );
1150 0 : if( FD_UNLIKELY( n==-1L && errno==EINTR ) ) continue;
1151 0 : if( FD_UNLIKELY( n<=0L ) ) FD_LOG_ERR(( "could not deliver the failover junk key" ));
1152 0 : off += (ulong)n;
1153 0 : }
1154 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1155 0 : tile->sign.failover_junk_fd = pipefd[ 0 ];
1156 0 : }
1157 0 : fd_keyload_unload( key, 0 );
1158 0 : }
1159 :
1160 : void
1161 : run_firedancer_init( config_t * config,
1162 : int init_workspaces,
1163 0 : int check_configure ) {
1164 0 : struct stat st;
1165 0 : int err = stat( config->paths.identity_key, &st );
1166 0 : if( FD_UNLIKELY( -1==err && errno==ENOENT ) ) FD_LOG_ERR(( "[paths.identity_key] key does not exist `%s`. You can generate an identity key at this path by running `%s keys new %s --config <toml>`", config->paths.identity_key, FD_BINARY_NAME, config->paths.identity_key ));
1167 0 : else if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "could not stat [paths.identity_key] `%s` (%i-%s)", config->paths.identity_key, errno, fd_io_strerror( errno ) ));
1168 :
1169 : /* Failover needs a fresh in-memory junk key. */
1170 0 : failover_junk_prepare( config );
1171 :
1172 : /* FIXME: fdctl_check_configure unconditionally checks for network
1173 : stack prerequisites even if the command being run does not
1174 : require networking. Hack around that here for now. */
1175 0 : if( check_configure ) fdctl_check_configure( config );
1176 0 : if( FD_LIKELY( init_workspaces ) ) initialize_workspaces( config );
1177 0 : initialize_stacks( config );
1178 0 : fd_bootinfo_write( config );
1179 0 : }
1180 :
1181 : void
1182 0 : initialize_accdb_fd( config_t const * config ) {
1183 : /* O_TRUNC of a previous run's accounts.db frees all its extents
1184 : synchronously. In development skip the truncate to keep reboots
1185 : fast. */
1186 0 : int oflags = O_RDWR|O_CREAT|O_NOATIME;
1187 0 : if( FD_LIKELY( !config->is_dev ) ) oflags |= O_TRUNC;
1188 :
1189 0 : int accounts_fd = open( config->paths.accounts, oflags, S_IRUSR|S_IWUSR );
1190 0 : if( FD_UNLIKELY( -1==accounts_fd ) ) FD_LOG_ERR(( "failed to open accounts.db (%i-%s)", errno, fd_io_strerror( errno ) ));
1191 0 : if( FD_UNLIKELY( -1==dup2( accounts_fd, FD_ACCDB_FD_RW ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1192 0 : if( FD_UNLIKELY( -1==close( accounts_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1193 :
1194 : /* Read-only fd for tiles (e.g. rpc) that consume accdb but must not
1195 : be able to mutate the on-disk file. Reopen via /proc/self/fd to
1196 : guarantee it refers to the same inode as the RW fd, avoiding any
1197 : race where the file at the path could be replaced between opens. */
1198 0 : char proc_path[ PATH_MAX ];
1199 0 : FD_TEST( fd_cstr_printf_check( proc_path, sizeof(proc_path), NULL, "/proc/self/fd/%d", FD_ACCDB_FD_RW ) );
1200 0 : int accounts_ro_fd = open( proc_path, O_RDONLY|O_NOATIME );
1201 0 : if( FD_UNLIKELY( -1==accounts_ro_fd ) ) FD_LOG_ERR(( "failed to open accounts.db read-only (%i-%s)", errno, fd_io_strerror( errno ) ));
1202 0 : if( FD_UNLIKELY( -1==dup2( accounts_ro_fd, FD_ACCDB_FD_RO ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1203 0 : if( FD_UNLIKELY( -1==close( accounts_ro_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1204 0 : }
1205 :
1206 : void
1207 0 : initialize_epoch_credits_fd( config_t const * config ) {
1208 0 : char const * spill_path = config->paths.epoch_credits;
1209 0 : int spill_fd = open( spill_path, O_RDWR|O_CREAT|O_TRUNC|O_NOATIME, S_IRUSR|S_IWUSR );
1210 0 : if( FD_UNLIKELY( -1==spill_fd ) ) FD_LOG_ERR(( "failed to open %s (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1211 0 : if( FD_UNLIKELY( -1==unlink( spill_path ) ) ) FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1212 :
1213 0 : if( FD_LIKELY( spill_fd!=FD_EPOCH_CREDITS_FD ) ) {
1214 0 : if( FD_UNLIKELY( -1==dup2( spill_fd, FD_EPOCH_CREDITS_FD ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1215 0 : if( FD_UNLIKELY( -1==close( spill_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1216 0 : }
1217 0 : }
1218 :
1219 : void
1220 0 : initialize_cost_tracker_fd( config_t const * config ) {
1221 0 : char const * spill_path = config->paths.cost_tracker;
1222 0 : int spill_fd = open( spill_path, O_RDWR|O_CREAT|O_TRUNC|O_NOATIME, S_IRUSR|S_IWUSR );
1223 0 : if( FD_UNLIKELY( -1==spill_fd ) ) FD_LOG_ERR(( "failed to open %s (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1224 0 : if( FD_UNLIKELY( -1==unlink( spill_path ) ) ) FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1225 :
1226 0 : if( FD_LIKELY( spill_fd!=FD_COST_TRACKER_FD ) ) {
1227 0 : if( FD_UNLIKELY( -1==dup2( spill_fd, FD_COST_TRACKER_FD ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1228 0 : if( FD_UNLIKELY( -1==close( spill_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1229 0 : }
1230 0 : }
1231 :
1232 : void
1233 0 : initialize_vote_stakes_fd( config_t const * config ) {
1234 0 : char const * spill_path = config->paths.vote_stakes;
1235 0 : int spill_fd = open( spill_path, O_RDWR|O_CREAT|O_TRUNC|O_NOATIME, S_IRUSR|S_IWUSR );
1236 0 : if( FD_UNLIKELY( -1==spill_fd ) ) FD_LOG_ERR(( "failed to open %s (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1237 0 : if( FD_UNLIKELY( -1==unlink( spill_path ) ) ) FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1238 :
1239 0 : if( FD_LIKELY( spill_fd!=FD_VOTE_STAKES_FD ) ) {
1240 0 : if( FD_UNLIKELY( -1==dup2( spill_fd, FD_VOTE_STAKES_FD ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1241 0 : if( FD_UNLIKELY( -1==close( spill_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1242 0 : }
1243 0 : }
1244 :
1245 : void
1246 0 : initialize_stake_delegations_fd( config_t const * config ) {
1247 0 : char const * spill_path = config->paths.stake_delegations;
1248 0 : int spill_fd = open( spill_path, O_RDWR|O_CREAT|O_TRUNC|O_NOATIME, S_IRUSR|S_IWUSR );
1249 0 : if( FD_UNLIKELY( -1==spill_fd ) ) FD_LOG_ERR(( "failed to open %s (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1250 0 : if( FD_UNLIKELY( -1==unlink( spill_path ) ) ) FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1251 :
1252 0 : if( FD_LIKELY( spill_fd!=FD_STAKE_DELEGATIONS_FD ) ) {
1253 0 : if( FD_UNLIKELY( -1==dup2( spill_fd, FD_STAKE_DELEGATIONS_FD ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1254 0 : if( FD_UNLIKELY( -1==close( spill_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1255 0 : }
1256 0 : }
1257 :
1258 : void
1259 0 : initialize_txncache_fd( config_t const * config ) {
1260 0 : char const * spill_path = config->paths.txncache;
1261 0 : int spill_fd = open( spill_path, O_RDWR|O_CREAT|O_TRUNC|O_NOATIME|O_DIRECT, S_IRUSR|S_IWUSR );
1262 0 : if( FD_UNLIKELY( -1==spill_fd ) ) FD_LOG_ERR(( "failed to open %s (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1263 0 : if( FD_UNLIKELY( -1==unlink( spill_path ) ) ) FD_LOG_ERR(( "unlink(%s) failed (%i-%s)", spill_path, errno, fd_io_strerror( errno ) ));
1264 :
1265 0 : if( FD_LIKELY( spill_fd!=FD_TXNCACHE_FD ) ) {
1266 0 : if( FD_UNLIKELY( -1==dup2( spill_fd, FD_TXNCACHE_FD ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1267 0 : if( FD_UNLIKELY( -1==close( spill_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1268 0 : }
1269 0 : }
1270 :
1271 : void
1272 0 : initialize_store_fds( config_t const * config ) {
1273 0 : fd_topo_t const * topo = &config->topo;
1274 0 : ulong store_obj_id = fd_pod_query_ulong( topo->props, "store", ULONG_MAX );
1275 0 : if( FD_UNLIKELY( store_obj_id==ULONG_MAX ) ) return;
1276 :
1277 0 : char const * path = fd_pod_queryf_cstr( topo->props, NULL, "obj.%lu.disk_path", store_obj_id );
1278 0 : ulong fec_max = fd_pod_queryf_ulong( topo->props, 0UL, "obj.%lu.fec_max", store_obj_id );
1279 0 : ulong fec_data_max = fd_pod_queryf_ulong( topo->props, 0UL, "obj.%lu.fec_data_max", store_obj_id );
1280 0 : ulong shred_storage_gib = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "obj.%lu.shred_storage_gib", store_obj_id );
1281 0 : ulong payload_slot_sz = fd_store_payload_slot_sz( fec_data_max );
1282 0 : ulong wire_off;
1283 0 : if( FD_UNLIKELY( !path || !fec_max || !payload_slot_sz || shred_storage_gib>FD_SHREDB_MAX_SIZE_GIB ||
1284 0 : __builtin_umull_overflow( fec_max, payload_slot_sz, &wire_off ) ) )
1285 0 : FD_LOG_ERR(( "invalid Store backing-file configuration" ));
1286 :
1287 0 : int store_fd = fd_store_file_create( path, wire_off, fd_shredb_max_shreds( shred_storage_gib ) );
1288 0 : if( FD_UNLIKELY( store_fd<0 ) )
1289 0 : FD_LOG_ERR(( "failed to create Store backing file `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
1290 0 : if( FD_LIKELY( store_fd!=FD_STORE_FD_RW ) ) {
1291 0 : if( FD_UNLIKELY( dup2( store_fd, FD_STORE_FD_RW )<0 ) ) FD_LOG_ERR(( "dup2(Store RW) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1292 0 : if( FD_UNLIKELY( close( store_fd ) ) ) FD_LOG_ERR(( "close(Store RW source) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1293 0 : }
1294 :
1295 : /* Reopen through procfs so the read-only descriptor is guaranteed to
1296 : name the same inode even if the configured path is replaced. */
1297 0 : char proc_path[ PATH_MAX ];
1298 0 : FD_TEST( fd_cstr_printf_check( proc_path, sizeof(proc_path), NULL, "/proc/self/fd/%d", FD_STORE_FD_RW ) );
1299 0 : int store_ro_fd = open( proc_path, O_RDONLY|O_NOATIME );
1300 0 : if( FD_UNLIKELY( store_ro_fd<0 ) )
1301 0 : FD_LOG_ERR(( "failed to open Store backing file read-only (%i-%s)", errno, fd_io_strerror( errno ) ));
1302 0 : if( FD_LIKELY( store_ro_fd!=FD_STORE_FD_RO ) ) {
1303 0 : if( FD_UNLIKELY( dup2( store_ro_fd, FD_STORE_FD_RO )<0 ) ) FD_LOG_ERR(( "dup2(Store RO) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1304 0 : if( FD_UNLIKELY( close( store_ro_fd ) ) ) FD_LOG_ERR(( "close(Store RO source) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1305 0 : }
1306 0 : }
1307 :
1308 : /* Snapshot production prep
1309 : (On startup, reconcile with files in snapshot dirs, and drop old
1310 : files.) */
1311 :
1312 : static int
1313 0 : matches_partial_snap_filename( char const * name ) {
1314 0 : if( FD_UNLIKELY( !strcmp( name, ".snapshot.tar.bz2-partial" ) ||
1315 0 : !strcmp( name, ".incremental-snapshot.tar.bz2-partial" ) ) ) return 1;
1316 :
1317 0 : if( *name=='.' ) name++;
1318 0 : char const * p;
1319 0 : if( !strncmp( name, "snapshot-x", 10UL ) ) p = name+10UL;
1320 0 : else if( !strncmp( name, "snapshot", 8UL ) ) p = name+8UL;
1321 0 : else return 0;
1322 0 : if( *p<'0' || *p>'9' ) return 0;
1323 0 : while( *p>='0' && *p<='9' ) p++;
1324 0 : return 0==strcmp( p, ".partial" );
1325 0 : }
1326 :
1327 : static int
1328 : snap_inode_desc( void const * _a,
1329 0 : void const * _b ) {
1330 0 : fd_backup_inode_t const * a = (fd_backup_inode_t const *)_a;
1331 0 : fd_backup_inode_t const * b = (fd_backup_inode_t const *)_b;
1332 0 : ulong a_slot = a->incr_slot==ULONG_MAX ? a->full_slot : a->incr_slot;
1333 0 : ulong b_slot = b->incr_slot==ULONG_MAX ? b->full_slot : b->incr_slot;
1334 0 : if( FD_LIKELY( a_slot!=b_slot ) ) return a_slot>b_slot ? -1 : 1;
1335 0 : return 0;
1336 0 : }
1337 :
1338 : static void
1339 : drop_old_snaps( int snap_dir_fd,
1340 : char const * snap_dir,
1341 : fd_backup_inode_t * snaps,
1342 : ulong * snap_cnt_p,
1343 0 : ulong snap_max ) {
1344 0 : ulong snap_cnt = *snap_cnt_p;
1345 0 : if( snap_cnt<=snap_max ) return;
1346 : /* drop oldest */
1347 0 : qsort( snaps, snap_cnt, sizeof(fd_backup_inode_t), snap_inode_desc );
1348 0 : for( ulong i=snap_max; i<snap_cnt; i++ ) {
1349 0 : int err = unlinkat( snap_dir_fd, snaps[ i ].name, 0 );
1350 0 : if( FD_UNLIKELY( err && errno!=ENOENT ) ) {
1351 0 : FD_LOG_WARNING(( "unlinkat(%s/%s) failed (%i-%s)", snap_dir, snaps[ i ].name, errno, fd_io_strerror( errno ) ));
1352 0 : } else if( FD_LIKELY( !err ) ) {
1353 0 : FD_LOG_INFO(( "deleted old snapshot `%s/%s`", snap_dir, snaps[ i ].name ));
1354 0 : }
1355 0 : }
1356 0 : *snap_cnt_p = snap_max;
1357 0 : }
1358 :
1359 : static void
1360 : snap_check_fd( char const * snap_dir,
1361 : char const * name,
1362 0 : int snap_fd ) {
1363 0 : struct stat st;
1364 0 : if( FD_UNLIKELY( -1==fstat( snap_fd, &st ) ) )
1365 0 : FD_LOG_ERR(( "fstat(%s/%s) failed (%i-%s)", snap_dir, name, errno, fd_io_strerror( errno ) ));
1366 :
1367 0 : if( FD_UNLIKELY( !S_ISREG( st.st_mode ) ) )
1368 0 : FD_LOG_ERR(( "snapshot `%s/%s` is not a regular file", snap_dir, name ));
1369 0 : }
1370 :
1371 : static void
1372 : snap_reperm_fd( char const * snap_dir,
1373 : char const * name,
1374 : int snap_fd,
1375 : uint uid,
1376 0 : uint gid ) {
1377 0 : if( FD_UNLIKELY( -1==fchown( snap_fd, uid, gid ) ) )
1378 0 : FD_LOG_ERR(( "fchown(%s/%s) failed (%i-%s)", snap_dir, name, errno, fd_io_strerror( errno ) ));
1379 :
1380 0 : if( FD_UNLIKELY( -1==fchmod( snap_fd, S_IRUSR|S_IWUSR ) ) )
1381 0 : FD_LOG_ERR(( "fchmod(%s/%s) failed (%i-%s)", snap_dir, name, errno, fd_io_strerror( errno ) ));
1382 0 : }
1383 :
1384 : ulong
1385 0 : initialize_snapshot_fds( config_t const * config ) {
1386 :
1387 0 : int download_enabled = fd_topo_find_tile( &config->topo, "snapct", 0UL )!=ULONG_MAX &&
1388 0 : (config->firedancer.snapshots.sources.gossip.allow_any ||
1389 0 : config->firedancer.snapshots.sources.gossip.allow_list_cnt ||
1390 0 : config->firedancer.snapshots.sources.servers_cnt);
1391 0 : int upload_enabled = fd_topo_find_tile( &config->topo, "snapsv", 0UL )!=ULONG_MAX;
1392 0 : int dio_enabled = fd_topo_find_tile( &config->topo, "snapzp", 0UL )!=ULONG_MAX;
1393 0 : fd_snap_pool_layout_t layout = fd_snap_pool_layout( config->firedancer.snapshots.max_full_snapshots_to_keep,
1394 0 : config->firedancer.snapshots.max_incremental_snapshots_to_keep,
1395 0 : fd_wfs_needs_incr( config->firedancer.snapshots.incremental_snapshots,
1396 0 : config->firedancer.consensus.wait_for_supermajority_at_slot,
1397 0 : !strcmp( config->firedancer.consensus.wait_for_supermajority_with_bank_hash, "" ),
1398 0 : (ulong)config->consensus.expected_shred_version ),
1399 0 : download_enabled );
1400 0 : ulong snap_full_max = layout.full_max;
1401 0 : ulong snap_incr_max = layout.incr_max;
1402 0 : ulong retained_snap_max = layout.retained_max;
1403 0 : ulong snap_max = layout.max;
1404 0 : if( FD_UNLIKELY( !snap_max ) ) return 0UL;
1405 :
1406 0 : char const * snap_dir = config->paths.snapshots;
1407 0 : int dir_fd = open( snap_dir, O_RDONLY|O_DIRECTORY|O_CLOEXEC );
1408 0 : if( FD_UNLIKELY( -1==dir_fd ) ) FD_LOG_ERR(( "open(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1409 :
1410 : /* This is additionally validated in fd_config_validatef() */
1411 0 : FD_CHECK_ERR( snap_max<=FD_SNAP_MAX, "[snapshots.max_{full,incremental}_snapshots_to_keep] is set too high" );
1412 0 : fd_backup_inode_t snap_full[ FD_SNAP_MAX ];
1413 0 : fd_backup_inode_t snap_incr[ FD_SNAP_MAX ];
1414 0 : fd_backup_inode_t scratch[ 2 ] = {
1415 0 : { .full_slot=ULONG_MAX, .incr_slot=ULONG_MAX },
1416 0 : { .full_slot=ULONG_MAX, .incr_slot=ULONG_MAX }
1417 0 : };
1418 0 : ulong snap_full_cnt = 0UL;
1419 0 : ulong snap_incr_cnt = 0UL;
1420 0 : FD_STATIC_ASSERT( sizeof(snap_full)+sizeof(snap_incr) < 2UL<<20, "stack overflow" );
1421 :
1422 : /* First, reconcile with the existing snapshots */
1423 0 : int dir_fd2 = dup( dir_fd );
1424 0 : if( FD_UNLIKELY( -1==dir_fd2 ) ) FD_LOG_ERR(( "dup(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1425 0 : DIR * dir = fdopendir( dir_fd2 );
1426 0 : if( FD_UNLIKELY( !dir ) ) FD_LOG_ERR(( "fdopendir(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1427 0 : struct dirent * entry;
1428 0 : for(;;) {
1429 0 : errno = 0;
1430 0 : entry = readdir( dir );
1431 0 : if( FD_UNLIKELY( !entry ) ) break;
1432 0 : if( FD_UNLIKELY( !strcmp( entry->d_name, ".") || !strcmp( entry->d_name, ".." ) ) ) continue;
1433 :
1434 : /* delete partial files */
1435 0 : if( FD_UNLIKELY( matches_partial_snap_filename( entry->d_name ) ) ) {
1436 0 : if( FD_UNLIKELY( -1==unlinkat( dir_fd, entry->d_name, 0 ) && errno!=ENOENT ) )
1437 0 : FD_LOG_ERR(( "unlinkat(%s/%s) failed (%i-%s)", snap_dir, entry->d_name, errno, fd_io_strerror( errno ) ));
1438 0 : continue;
1439 0 : }
1440 :
1441 : /* decode file name */
1442 0 : int is_zstd;
1443 0 : ulong entry_full_slot, entry_incremental_slot;
1444 0 : uchar decoded_hash[ FD_HASH_FOOTPRINT ];
1445 0 : if( FD_UNLIKELY( -1==fd_ssarchive_parse_filename( entry->d_name, &entry_full_slot, &entry_incremental_slot, decoded_hash, &is_zstd ) ) ) continue;
1446 0 : if( FD_UNLIKELY( strlen( entry->d_name )>=FD_SNAP_NAME_MAX ) ) continue;
1447 0 : fd_backup_inode_t inode = (fd_backup_inode_t){
1448 0 : .full_slot = entry_full_slot,
1449 0 : .incr_slot = entry_incremental_slot
1450 0 : };
1451 0 : fd_cstr_ncpy( inode.name, entry->d_name, FD_SNAP_NAME_MAX );
1452 :
1453 : /* register snapshot (and lazily delete snaps if there are too many)
1454 : the lazy drop must free a slot for the append below, even if
1455 : retention is configured at the hard capacity */
1456 0 : if( entry_incremental_slot==ULONG_MAX ) {
1457 0 : if( FD_UNLIKELY( snap_full_cnt>=FD_SNAP_MAX ) ) {
1458 0 : drop_old_snaps( dir_fd, snap_dir, snap_full, &snap_full_cnt, fd_ulong_min( snap_full_max, FD_SNAP_MAX-1UL ) );
1459 0 : }
1460 0 : snap_full[ snap_full_cnt++ ] = inode;
1461 0 : } else {
1462 0 : if( FD_UNLIKELY( snap_incr_cnt>=FD_SNAP_MAX ) ) {
1463 0 : drop_old_snaps( dir_fd, snap_dir, snap_incr, &snap_incr_cnt, fd_ulong_min( snap_incr_max, FD_SNAP_MAX-1UL ) );
1464 0 : }
1465 0 : snap_incr[ snap_incr_cnt++ ] = inode;
1466 0 : }
1467 0 : }
1468 0 : if( FD_UNLIKELY( errno ) ) FD_LOG_ERR(( "readdir(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1469 0 : if( FD_UNLIKELY( -1==closedir( dir ) ) ) FD_LOG_ERR(( "closedir(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1470 :
1471 : /* FIXME this should be smart enough to drop incremental snapshots
1472 : that no longer have a matching full snapshot. */
1473 0 : drop_old_snaps( dir_fd, snap_dir, snap_full, &snap_full_cnt, snap_full_max );
1474 0 : drop_old_snaps( dir_fd, snap_dir, snap_incr, &snap_incr_cnt, snap_incr_max );
1475 :
1476 : /* zero pad */
1477 0 : for( ulong i=snap_full_cnt; i<snap_full_max; i++ ) snap_full[ i ] = (fd_backup_inode_t){ .full_slot=ULONG_MAX, .incr_slot=ULONG_MAX };
1478 0 : for( ulong i=snap_incr_cnt; i<snap_incr_max; i++ ) snap_incr[ i ] = (fd_backup_inode_t){ .full_slot=ULONG_MAX, .incr_slot=ULONG_MAX };
1479 :
1480 0 : for( ulong i=0UL; i<snap_max; i++ ) {
1481 0 : fd_backup_inode_t * inode;
1482 0 : if( i<snap_full_max ) inode = &snap_full[ i ];
1483 0 : else if( i<retained_snap_max ) inode = &snap_incr[ i-snap_full_max ];
1484 0 : else inode = &scratch [ i-retained_snap_max ];
1485 :
1486 0 : int snap_fd;
1487 0 : if( FD_UNLIKELY( !inode->name[ 0 ] ) ) {
1488 0 : fd_snap_pool_partial_name( inode->name, (uint)i );
1489 0 : snap_fd = openat( dir_fd, inode->name, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, S_IRUSR|S_IWUSR );
1490 0 : if( FD_UNLIKELY( -1==snap_fd ) ) FD_LOG_ERR(( "openat(%s/%s) failed (%i-%s)", snap_dir, inode->name, errno, fd_io_strerror( errno ) ));
1491 0 : } else {
1492 0 : snap_fd = openat( dir_fd, inode->name, O_RDWR|O_CLOEXEC|O_NOFOLLOW );
1493 0 : if( FD_UNLIKELY( -1==snap_fd ) ) FD_LOG_ERR(( "openat(%s/%s) failed (%i-%s)", snap_dir, inode->name, errno, fd_io_strerror( errno ) ));
1494 0 : snap_check_fd( snap_dir, inode->name, snap_fd );
1495 0 : }
1496 :
1497 0 : snap_reperm_fd( snap_dir, inode->name, snap_fd, config->uid, config->gid );
1498 :
1499 0 : if( FD_UNLIKELY( -1==dup2( snap_fd, FD_SNAP_FD( i ) ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1500 0 : if( FD_UNLIKELY( -1==close( snap_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1501 :
1502 0 : if( dio_enabled ) {
1503 0 : int dio_fd = openat( dir_fd, inode->name, O_WRONLY|O_DIRECT );
1504 0 : if( FD_UNLIKELY( -1==dio_fd ) ) FD_LOG_ERR(( "openat(%s/%s, O_DIRECT) failed (%i-%s)", snap_dir, inode->name, errno, fd_io_strerror( errno ) ));
1505 0 : if( FD_UNLIKELY( -1==dup2( dio_fd, FD_SNAP_DIO_FD( i ) ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1506 0 : if( FD_UNLIKELY( -1==close( dio_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1507 0 : }
1508 :
1509 0 : if( upload_enabled ) {
1510 0 : int ro_fd = openat( dir_fd, inode->name, O_RDONLY|O_NOFOLLOW );
1511 0 : if( FD_UNLIKELY( -1==ro_fd ) ) FD_LOG_ERR(( "openat(%s/%s, O_RDONLY) failed (%i-%s)", snap_dir, inode->name, errno, fd_io_strerror( errno ) ));
1512 0 : if( FD_UNLIKELY( -1==dup2( ro_fd, FD_SNAP_RO_FD( i ) ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1513 0 : if( FD_UNLIKELY( -1==close( ro_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1514 0 : }
1515 0 : }
1516 :
1517 0 : if( FD_UNLIKELY( -1==close( dir_fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", snap_dir, errno, fd_io_strerror( errno ) ));
1518 0 : return snap_max;
1519 0 : }
1520 :
1521 : /* The boot sequence is a little bit involved...
1522 :
1523 : A process tree is created that looks like,
1524 :
1525 : + main
1526 : +-- pidns
1527 : +-- tile 0
1528 : +-- tile 1
1529 : ...
1530 :
1531 : What we want is that if any process in the tree dies, all other
1532 : processes will also die. This is done as follows,
1533 :
1534 : (a) pidns is the init process of a PID namespace, so if it dies the
1535 : kernel will terminate the child processes.
1536 :
1537 : (b) main is the parent of pidns, so it can issue a waitpid() on the
1538 : child PID, and when it completes terminate itself.
1539 :
1540 : (c) pidns is the parent of tiles, so it could issue a waitpid() of
1541 : -1 to wait for any of them to terminate, but how would it know
1542 : if main has died?
1543 :
1544 : (d) main creates a pipe, and passes the write end to pidns. If main
1545 : dies, the pipe will be closed, and pidns will get a HUP on the
1546 : read end. Then pidns creates a pipe per child and passes the
1547 : write end to the child. If any of the children die, the pipe
1548 : will be closed, and pidns will get a HUP on the read end.
1549 :
1550 : Then pidns can call poll() on both the write end of the main
1551 : pipe and the read end of all the child pipes. If any of them
1552 : raises SIGHUP, then pidns knows that the parent or a child has
1553 : died, and it can terminate itself, which due to (a) and (b)
1554 : will kill all other processes. */
1555 : void
1556 : run_firedancer( config_t * config,
1557 : int parent_pipefd,
1558 0 : int init_workspaces ) {
1559 : /* dump the topology we are using to the output log */
1560 0 : fd_topo_print_log( 0, &config->topo );
1561 :
1562 0 : run_firedancer_init( config, init_workspaces, 1 );
1563 :
1564 0 : if( FD_UNLIKELY( close( 0 ) ) ) FD_LOG_ERR(( "close(0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1565 0 : if( FD_UNLIKELY( fd_log_private_logfile_fd()!=1 && close( 1 ) ) ) FD_LOG_ERR(( "close(1) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
1566 :
1567 0 : int pipefd;
1568 0 : pid_namespace = clone_firedancer( config, parent_pipefd, &pipefd );
1569 :
1570 : /* Print the location of the logfile on SIGINT or SIGTERM, and also
1571 : kill the child. They are connected by a pipe which the child is
1572 : polling so we don't strictly need to kill the child, but its helpful
1573 : to do that before printing the log location line, else it might
1574 : get interleaved due to timing windows in the shutdown. */
1575 0 : install_parent_signals();
1576 :
1577 0 : struct sock_filter seccomp_filter[ 128UL ];
1578 0 : populate_sock_filter_policy_main( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd(), (uint)pid_namespace );
1579 :
1580 0 : int allow_fds[ 4 ];
1581 0 : ulong allow_fds_cnt = 0;
1582 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
1583 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
1584 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile, or maybe stdout */
1585 0 : allow_fds[ allow_fds_cnt++ ] = pipefd; /* read end of main pipe */
1586 0 : if( FD_UNLIKELY( parent_pipefd!=-1 ) )
1587 0 : allow_fds[ allow_fds_cnt++ ] = parent_pipefd; /* write end of parent pipe */
1588 :
1589 0 : if( FD_LIKELY( config->development.sandbox ) ) {
1590 0 : fd_sandbox_enter( config->uid,
1591 0 : config->gid,
1592 0 : 0,
1593 0 : 0,
1594 0 : 0,
1595 0 : 1, /* Keep controlling terminal for main so it can receive Ctrl+C */
1596 0 : 0,
1597 0 : 0UL,
1598 0 : 0UL,
1599 0 : 0UL,
1600 0 : 0UL,
1601 0 : allow_fds_cnt,
1602 0 : allow_fds,
1603 0 : sock_filter_policy_main_instr_cnt,
1604 0 : seccomp_filter );
1605 0 : } else {
1606 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
1607 0 : }
1608 :
1609 : /* the only clean way to exit is SIGINT or SIGTERM on this parent process,
1610 : so if wait4() completes, it must be an error */
1611 0 : int wstatus;
1612 0 : if( FD_UNLIKELY( -1==wait4( pid_namespace, &wstatus, (int)__WALL, NULL ) ) )
1613 0 : FD_LOG_ERR(( "main wait4() failed (%i-%s)\nLog at \"%s\"", errno, fd_io_strerror( errno ), fd_log_private_path ));
1614 :
1615 0 : if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) ) fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
1616 0 : else fd_sys_util_exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
1617 0 : }
1618 :
1619 : void
1620 : run_cmd_fn( args_t * args FD_PARAM_UNUSED,
1621 0 : config_t * config ) {
1622 0 : #define CHECK_PORT_NON_ZERO( field ) \
1623 0 : if( FD_UNLIKELY( config->field==0 ) ) { \
1624 0 : FD_LOG_ERR(( #field " is not set in your configuration file. Please set it to a non-zero value." )); \
1625 0 : }
1626 :
1627 0 : if( FD_UNLIKELY( !config->gossip.entrypoints_cnt && !config->development.bootstrap ) )
1628 0 : FD_LOG_ERR(( "No entrypoints specified in configuration file under [gossip.entrypoints], but "
1629 0 : "at least one is needed to determine how to connect to the Solana cluster. If "
1630 0 : "you want to start a new cluster in a development environment, use `fddev` instead "
1631 0 : "of `fdctl`. If you want to use an existing genesis, set [development.bootstrap] "
1632 0 : "to \"true\" in the configuration file." ));
1633 :
1634 0 : for( ulong i=0; i<config->gossip.entrypoints_cnt; i++ ) {
1635 0 : if( FD_UNLIKELY( !strcmp( config->gossip.entrypoints[ i ], "" ) ) )
1636 0 : FD_LOG_ERR(( "One of the entrypoints in your configuration file under [gossip.entrypoints] is "
1637 0 : "empty. Please remove the empty entrypoint or set it correctly. "));
1638 0 : }
1639 :
1640 0 : CHECK_PORT_NON_ZERO( gossip.port );
1641 0 : CHECK_PORT_NON_ZERO( tiles.quic.quic_transaction_listen_port );
1642 0 : CHECK_PORT_NON_ZERO( tiles.quic.regular_transaction_listen_port );
1643 0 : CHECK_PORT_NON_ZERO( tiles.shred.shred_listen_port );
1644 0 : CHECK_PORT_NON_ZERO( tiles.metric.prometheus_listen_port );
1645 0 : CHECK_PORT_NON_ZERO( tiles.gui.gui_listen_port );
1646 :
1647 0 : #undef CHECK_PORT_NON_ZERO
1648 :
1649 0 : run_firedancer( config, -1, 1 );
1650 0 : }
1651 :
1652 : static void
1653 0 : run1_args_help( fd_action_help_t * help ) {
1654 0 : fd_action_help_arg( help, "<tile-name>", NULL, "Type of tile to run (e.g. `net`, `quic`, `replay`). A tile is a single\n"
1655 0 : "thread pinned to a CPU core that performs one part of the validator's work" );
1656 0 : fd_action_help_arg( help, "<kind-id>", NULL, "Zero-based index selecting which instance of that tile type to run\n"
1657 0 : "when the topology has more than one" );
1658 0 : fd_action_help_arg( help, "--pipe-fd", "<fd>", "Internal use: file descriptor over which the parent supervisor process\n"
1659 0 : "communicates with this tile (default -1, standalone)" );
1660 0 : }
1661 :
1662 : action_t fd_action_run1 = {
1663 : .name = "run1",
1664 : .args = run1_cmd_args,
1665 : .fn = run1_cmd_fn,
1666 : .perm = NULL,
1667 : .description = "Start up a single Firedancer tile",
1668 : .detail = "Runs one tile of the validator topology in the current process. A tile is a\n"
1669 : "single thread pinned to a CPU core that performs one part of the validator's\n"
1670 : "work. This is primarily an internal command used by `run` to spawn individual\n"
1671 : "tiles; most operators should use `run` instead.",
1672 : .usage = "run1 <tile-name> <kind-id> [OPTIONS]",
1673 : .args_help = run1_args_help,
1674 : };
1675 :
1676 : action_t fd_action_run = {
1677 : .name = "run",
1678 : .args = NULL,
1679 : .fn = run_cmd_fn,
1680 : .require_config = 1,
1681 : .perm = run_cmd_perm,
1682 : .description = "Start up a Firedancer validator",
1683 : .detail = "Boots and runs the full validator described by the configuration file. This\n"
1684 : "is the main command operators use to run Firedancer. It must be started with\n"
1685 : "sufficient privileges to perform boot-time setup, after which it drops\n"
1686 : "privileges to the configured user.",
1687 : .usage = "run [OPTIONS]",
1688 : .permission_err = "insufficient permissions to execute command `%s`. It is recommended "
1689 : "to start Firedancer as the root user, but you can also start it "
1690 : "with the missing capabilities listed above. The program only needs "
1691 : "to start with elevated permissions to do privileged operations at "
1692 : "boot, and will immediately drop permissions and switch to the user "
1693 : "specified in your configuration file once they are complete. Firedancer "
1694 : "will not execute outside of the boot process as root, and will refuse "
1695 : "to start if it cannot drop privileges. Firedancer needs to be started "
1696 : "privileged to configure high performance networking with XDP.",
1697 : };
|