Line data Source code
1 : #define _GNU_SOURCE
2 : #include "run.h"
3 : #include "../../../../flamenco/accdb/fd_accdb.h"
4 :
5 : #include <sys/wait.h>
6 : #include "generated/main_seccomp.h"
7 : #if defined(__aarch64__)
8 : #include "generated/pidns_arm64_seccomp.h"
9 : #else
10 : #include "generated/pidns_seccomp.h"
11 : #endif
12 :
13 : #include "../../fd_bootinfo.h"
14 : #include "../../../platform/fd_sys_util.h"
15 : #include "../../../platform/fd_file_util.h"
16 : #include "../../../platform/fd_net_util.h"
17 : #include "../../../../disco/net/fd_net_tile.h"
18 :
19 : #include "../configure/configure.h"
20 :
21 : #include <dirent.h>
22 : #include <sched.h>
23 : #include <stdio.h>
24 : #include <stdlib.h> /* getenv */
25 : #include <poll.h>
26 : #include <unistd.h>
27 : #include <errno.h>
28 : #include <fcntl.h>
29 : #include <sys/prctl.h>
30 : #include <sys/resource.h>
31 : #include <sys/mman.h>
32 : #include <sys/stat.h>
33 : #include <linux/capability.h>
34 :
35 : #include "../../../../util/tile/fd_tile_private.h"
36 :
37 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
38 :
39 0 : #define NAME "run"
40 :
41 : void
42 : run_cmd_perm( args_t * args,
43 : fd_cap_chk_t * chk,
44 0 : config_t const * config ) {
45 0 : (void)args;
46 :
47 0 : ulong mlock_limit = fd_topo_mlock_max_tile( &config->topo );
48 :
49 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)`" );
50 0 : fd_cap_chk_raise_rlimit( chk, NAME, RLIMIT_NICE, 40, "call `setpriority(2)` to increase thread priorities" );
51 0 : fd_cap_chk_raise_rlimit( chk, NAME, RLIMIT_NOFILE, CONFIGURE_NR_OPEN_FILES,
52 0 : "call `rlimit(2) to increase `RLIMIT_NOFILE` to allow more open files for Agave" );
53 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_RAW, "call `socket(2)` to bind to a raw socket for use by XDP" );
54 0 : fd_cap_chk_cap( chk, NAME, CAP_SYS_ADMIN, "call `bpf(2)` with the `BPF_OBJ_GET` command to initialize XDP" );
55 0 : if( fd_sandbox_requires_cap_sys_admin( config->uid, config->gid ) )
56 0 : fd_cap_chk_cap( chk, NAME, CAP_SYS_ADMIN, "call `unshare(2)` with `CLONE_NEWUSER` to sandbox the process in a user namespace" );
57 0 : if( FD_LIKELY( getuid() != config->uid ) )
58 0 : fd_cap_chk_cap( chk, NAME, CAP_SETUID, "call `setresuid(2)` to switch uid to the sandbox user" );
59 0 : if( FD_LIKELY( getgid()!=config->gid ) )
60 0 : fd_cap_chk_cap( chk, NAME, CAP_SETGID, "call `setresgid(2)` to switch gid to the sandbox user" );
61 0 : if( FD_UNLIKELY( config->tiles.metric.prometheus_listen_port<1024 ) )
62 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving metrics" );
63 0 : if( FD_UNLIKELY( config->tiles.gui.gui_listen_port<1024 ) )
64 0 : fd_cap_chk_cap( chk, NAME, CAP_NET_BIND_SERVICE, "call `bind(2)` to bind to a privileged port for serving the GUI" );
65 0 : }
66 :
67 : struct pidns_clone_args {
68 : config_t const * config;
69 : int * pipefd;
70 : int closefd;
71 : };
72 :
73 : extern char fd_log_private_path[ 1024 ]; /* empty string on start */
74 :
75 : static pid_t pid_namespace;
76 :
77 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)
78 :
79 : static void
80 0 : parent_signal( int sig ) {
81 0 : if( FD_LIKELY( pid_namespace ) ) kill( pid_namespace, SIGKILL );
82 :
83 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() ));
84 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() ));
85 :
86 0 : if( FD_LIKELY( sig==SIGINT ) ) fd_sys_util_exit_group( 128+SIGINT );
87 0 : else fd_sys_util_exit_group( 0 );
88 0 : }
89 :
90 : static void
91 0 : install_parent_signals( void ) {
92 0 : struct sigaction sa = {
93 0 : .sa_handler = parent_signal,
94 0 : .sa_flags = 0,
95 0 : };
96 0 : if( FD_UNLIKELY( sigaction( SIGTERM, &sa, NULL ) ) )
97 0 : FD_LOG_ERR(( "sigaction(SIGTERM) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
98 0 : if( FD_UNLIKELY( sigaction( SIGINT, &sa, NULL ) ) )
99 0 : FD_LOG_ERR(( "sigaction(SIGINT) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
100 :
101 0 : sa.sa_handler = SIG_IGN;
102 0 : if( FD_UNLIKELY( sigaction( SIGUSR1, &sa, NULL ) ) )
103 0 : FD_LOG_ERR(( "sigaction(SIGUSR1) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
104 0 : if( FD_UNLIKELY( sigaction( SIGUSR2, &sa, NULL ) ) )
105 0 : FD_LOG_ERR(( "sigaction(SIGUSR2) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
106 0 : }
107 :
108 : void *
109 0 : create_clone_stack( void ) {
110 0 : ulong mmap_sz = FD_TILE_PRIVATE_STACK_SZ + 2UL*FD_SHMEM_NORMAL_PAGE_SZ;
111 0 : uchar * stack = (uchar *)mmap( NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0 );
112 0 : if( FD_UNLIKELY( stack==MAP_FAILED ) )
113 0 : FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
114 :
115 : /* Make space for guard lo and guard hi */
116 0 : if( FD_UNLIKELY( munmap( stack, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
117 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
118 0 : stack += FD_SHMEM_NORMAL_PAGE_SZ;
119 0 : if( FD_UNLIKELY( munmap( stack + FD_TILE_PRIVATE_STACK_SZ, FD_SHMEM_NORMAL_PAGE_SZ ) ) )
120 0 : FD_LOG_ERR(( "munmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
121 :
122 : /* Create the guard regions in the extra space */
123 0 : void * guard_lo = (void *)(stack - FD_SHMEM_NORMAL_PAGE_SZ );
124 0 : if( FD_UNLIKELY( mmap( guard_lo, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
125 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_lo ) )
126 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
127 :
128 0 : void * guard_hi = (void *)(stack + FD_TILE_PRIVATE_STACK_SZ);
129 0 : if( FD_UNLIKELY( mmap( guard_hi, FD_SHMEM_NORMAL_PAGE_SZ, PROT_NONE,
130 0 : MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, (off_t)0 )!=guard_hi ) )
131 0 : FD_LOG_ERR(( "mmap failed (%i-%s)", errno, fd_io_strerror( errno ) ));
132 :
133 0 : return stack;
134 0 : }
135 :
136 :
137 : static int
138 : execve_agave( int config_memfd,
139 0 : int pipefd ) {
140 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 ) ));
141 0 : pid_t child = fork();
142 0 : if( FD_UNLIKELY( -1==child ) ) FD_LOG_ERR(( "fork() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
143 0 : if( FD_LIKELY( !child ) ) {
144 0 : char _current_executable_path[ PATH_MAX ];
145 0 : FD_TEST( -1!=fd_file_util_self_exe( _current_executable_path ) );
146 :
147 0 : char config_fd[ 32 ];
148 0 : FD_TEST( fd_cstr_printf_check( config_fd, sizeof( config_fd ), NULL, "%d", config_memfd ) );
149 0 : char * args[ 5 ] = { _current_executable_path, "run-agave", "--config-fd", config_fd, NULL };
150 :
151 0 : char * envp[] = { NULL, NULL };
152 0 : char * google_creds = getenv( "GOOGLE_APPLICATION_CREDENTIALS" );
153 0 : char provide_creds[ PATH_MAX+30UL ];
154 0 : if( FD_UNLIKELY( google_creds ) ) {
155 0 : FD_TEST( fd_cstr_printf_check( provide_creds, sizeof( provide_creds ), NULL, "GOOGLE_APPLICATION_CREDENTIALS=%s", google_creds ) );
156 0 : envp[ 0 ] = provide_creds;
157 0 : }
158 :
159 0 : if( FD_UNLIKELY( -1==execve( _current_executable_path, args, envp ) ) ) FD_LOG_ERR(( "execve() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
160 0 : } else {
161 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 ) ));
162 0 : return child;
163 0 : }
164 0 : return 0;
165 0 : }
166 :
167 : static int
168 0 : cgroup_procs_write( char const * path ) {
169 0 : int fd = open( path, O_WRONLY );
170 0 : if( FD_UNLIKELY( fd<0 ) ) {
171 0 : if( FD_LIKELY( errno==ENOENT ) ) return 0;
172 0 : FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
173 0 : }
174 :
175 0 : char pid[ 32 ];
176 0 : ulong pid_len;
177 0 : FD_TEST( fd_cstr_printf_check( pid, sizeof(pid), &pid_len, "%ld", (long)getpid() ) );
178 0 : if( FD_UNLIKELY( write( fd, pid, pid_len )!=(long)pid_len ) )
179 0 : FD_LOG_ERR(( "write(%s,\"%s\") failed (%i-%s)", path, pid, errno, fd_io_strerror( errno ) ));
180 0 : if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
181 0 : return 1;
182 0 : }
183 :
184 : struct spawn_cgroup {
185 : int probed; /* isolation cgroup existence checked, original saved */
186 : int present; /* isolation cgroup exists */
187 : int joined; /* currently a member of the isolation cgroup */
188 : char isolation[ PATH_MAX ];
189 : char original[ PATH_MAX ];
190 : };
191 :
192 : static void
193 : join_isolation_cgroup( char const * name,
194 0 : struct spawn_cgroup * cg ) {
195 0 : if( FD_UNLIKELY( !cg->probed ) ) {
196 0 : cg->probed = 1;
197 0 : FD_TEST( fd_cstr_printf_check( cg->isolation, sizeof(cg->isolation), NULL, "/sys/fs/cgroup/%s/cgroup.procs", name ) );
198 :
199 : /* The cpuset stage not being configured (no cgroup) is the common
200 : case and must be decided FIRST: on cgroup v1-only or hybrid
201 : hosts /proc/self/cgroup does not have the v2 format, and
202 : validating it before knowing the stage is even in use would
203 : turn every tile launch on such hosts into a fatal error. */
204 0 : cg->present = !access( cg->isolation, F_OK );
205 0 : if( FD_LIKELY( !cg->present ) ) return;
206 :
207 : /* Remember where we came from. The v2 entry in /proc/self/cgroup
208 : is the line "0::<path>"; on a pure v2 hierarchy it is the only
209 : line, but on hybrid systems v1 controller lines precede it, so
210 : search rather than assume. */
211 0 : char buf[ 4096 ];
212 0 : int fd = open( "/proc/self/cgroup", O_RDONLY );
213 0 : if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
214 0 : long n = read( fd, buf, sizeof(buf)-1UL );
215 0 : if( FD_UNLIKELY( n<0L ) ) FD_LOG_ERR(( "read(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
216 0 : if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(/proc/self/cgroup) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
217 0 : buf[ n ] = '\0';
218 :
219 0 : char * line = buf;
220 0 : while( line && strncmp( line, "0::", 3UL ) ) {
221 0 : line = strchr( line, '\n' );
222 0 : if( FD_LIKELY( line ) ) line++;
223 0 : }
224 0 : if( FD_UNLIKELY( !line || !line[ 0 ] ) )
225 0 : FD_LOG_ERR(( "no cgroup v2 entry in /proc/self/cgroup while the cpuset isolation cgroup `/sys/fs/cgroup/%s` "
226 0 : "exists. Remove it with `%s configure fini cpuset`", name, FD_BINARY_NAME ));
227 0 : char * nl = strchr( line, '\n' ); if( FD_LIKELY( nl ) ) *nl = '\0';
228 0 : FD_TEST( fd_cstr_printf_check( cg->original, PATH_MAX, NULL,
229 0 : "/sys/fs/cgroup%s/cgroup.procs", line+3UL ) );
230 0 : }
231 :
232 0 : if( FD_UNLIKELY( !cg->present || cg->joined ) ) return;
233 0 : cg->joined = cgroup_procs_write( cg->isolation );
234 0 : }
235 :
236 : static void
237 0 : leave_isolation_cgroup( struct spawn_cgroup * cg ) {
238 0 : if( FD_LIKELY( !cg->joined ) ) return;
239 0 : if( FD_UNLIKELY( !cgroup_procs_write( cg->original ) ) ) FD_LOG_ERR(( "could not return to original cgroup `%s`", cg->original ));
240 0 : cg->joined = 0;
241 0 : }
242 :
243 : static pid_t
244 : execve_tile( char const * name,
245 : fd_topo_tile_t const * tile,
246 : fd_cpuset_t const * floating_cpu_set,
247 : int floating_priority,
248 : int config_memfd,
249 : int pipefd,
250 0 : struct spawn_cgroup * cg ) {
251 0 : FD_CPUSET_DECL( cpu_set );
252 0 : if( FD_LIKELY( tile->cpu_idx!=ULONG_MAX ) ) {
253 : /* Join the cpuset isolation cgroup (if configured) and set the
254 : thread affinity before we clone the new process, to ensure
255 : kernel first touch happens on the desired thread. The child
256 : inherits both. */
257 0 : join_isolation_cgroup( name, cg );
258 0 : 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_memcpy( cpu_set, floating_cpu_set, fd_cpuset_footprint() );
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 : char _current_executable_path[ PATH_MAX ];
283 0 : FD_TEST( -1!=fd_file_util_self_exe( _current_executable_path ) );
284 :
285 0 : char kind_id[ 32 ], config_fd[ 32 ], pipe_fd[ 32 ];
286 0 : FD_TEST( fd_cstr_printf_check( kind_id, sizeof( kind_id ), NULL, "%lu", tile->kind_id ) );
287 0 : FD_TEST( fd_cstr_printf_check( config_fd, sizeof( config_fd ), NULL, "%d", config_memfd ) );
288 0 : FD_TEST( fd_cstr_printf_check( pipe_fd, sizeof( pipe_fd ), NULL, "%d", pipefd ) );
289 0 : char const * args[ 9 ] = { _current_executable_path, "run1", tile->name, kind_id, "--pipe-fd", pipe_fd, "--config-fd", config_fd, NULL };
290 0 : if( FD_UNLIKELY( -1==execve( _current_executable_path, (char **)args, NULL ) ) ) FD_LOG_ERR(( "execve() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
291 0 : } else {
292 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 ) ));
293 0 : return child;
294 0 : }
295 0 : return 0;
296 0 : }
297 :
298 : int
299 0 : main_pid_namespace( void * _args ) {
300 0 : struct pidns_clone_args * args = _args;
301 0 : if( FD_UNLIKELY( close( args->pipefd[ 0 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
302 0 : if( FD_UNLIKELY( -1!=args->closefd ) ) {
303 0 : if( FD_UNLIKELY( close( args->closefd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
304 0 : }
305 :
306 0 : config_t const * config = args->config;
307 :
308 0 : fd_log_thread_set( "pidns" );
309 0 : ulong pid = fd_sandbox_getpid(); /* Need to read /proc again.. we got a new PID from clone */
310 0 : fd_log_private_group_id_set( pid );
311 0 : fd_log_private_thread_id_set( pid );
312 0 : fd_log_private_stack_discover( FD_TILE_PRIVATE_STACK_SZ,
313 0 : &fd_tile_private_stack0, &fd_tile_private_stack1 );
314 :
315 0 : if( FD_UNLIKELY( !config->development.sandbox ) ) {
316 : /* If no sandbox, then there's no actual PID namespace so we can't
317 : wait() grandchildren for the exit code. Do this as a workaround. */
318 0 : if( FD_UNLIKELY( -1==prctl( PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0 ) ) )
319 0 : FD_LOG_ERR(( "prctl(PR_SET_CHILD_SUBREAPER) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
320 0 : }
321 :
322 : /* Save the current affinity, it will be restored after creating any child tiles */
323 0 : FD_CPUSET_DECL( floating_cpu_set );
324 0 : if( FD_UNLIKELY( fd_cpuset_getaffinity( 0, floating_cpu_set ) ) )
325 0 : FD_LOG_ERR(( "fd_cpuset_getaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
326 :
327 0 : pid_t child_pids[ FD_TOPO_MAX_TILES+1 ];
328 0 : ulong actual_pids[ FD_TOPO_MAX_TILES+1 ];
329 0 : for( ulong i=0UL; i<FD_TOPO_MAX_TILES+1; i++ ) actual_pids[ i ] = ULONG_MAX;
330 0 : char child_names[ FD_TOPO_MAX_TILES+1 ][ 32 ];
331 0 : ulong child_idxs[ FD_TOPO_MAX_TILES+1 ];
332 0 : struct pollfd fds[ FD_TOPO_MAX_TILES+2 ];
333 :
334 0 : int config_memfd = fd_config_to_memfd( config );
335 0 : if( FD_UNLIKELY( -1==config_memfd ) ) FD_LOG_ERR(( "fd_config_to_memfd() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
336 :
337 0 : ulong child_cnt = 0UL;
338 0 : if( FD_LIKELY( !config->is_firedancer && !config->development.no_agave ) ) {
339 0 : int pipefd[ 2 ];
340 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
341 0 : fds[ child_cnt ] = (struct pollfd){ .fd = pipefd[ 0 ], .events = 0 };
342 0 : child_pids[ child_cnt ] = execve_agave( config_memfd, pipefd[ 1 ] );
343 0 : FD_TEST( child_pids[ child_cnt ]>0 );
344 0 : actual_pids[ child_cnt ] = (ulong)child_pids[ child_cnt ];
345 0 : child_idxs[ child_cnt ] = ULONG_MAX;
346 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
347 0 : strncpy( child_names[ child_cnt ], "agave", 32 );
348 0 : child_cnt++;
349 0 : }
350 :
351 0 : errno = 0;
352 0 : int save_priority = getpriority( PRIO_PROCESS, 0 );
353 0 : if( FD_UNLIKELY( -1==save_priority && errno ) ) FD_LOG_ERR(( "getpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
354 :
355 0 : int need_xdp = 0==strcmp( config->net.provider, "xdp" );
356 0 : fd_xdp_fds_t xdp_fds[ FD_TOPO_XDP_FDS_MAX ];
357 0 : uint xdp_fds_cnt = FD_TOPO_XDP_FDS_MAX;
358 0 : if( need_xdp ) {
359 0 : fd_topo_install_xdp( &config->topo, xdp_fds, &xdp_fds_cnt, config->net.bind_address_parsed, 0 );
360 0 : }
361 :
362 0 : initialize_accdb_fd( config );
363 :
364 0 : struct spawn_cgroup spawn_cg = {0};
365 :
366 0 : for( ulong pass=0UL; pass<2UL; pass++ ) {
367 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
368 0 : fd_topo_tile_t const * tile = &config->topo.tiles[ i ];
369 0 : if( FD_UNLIKELY( tile->is_agave ) ) continue;
370 0 : if( FD_UNLIKELY( (tile->cpu_idx!=ULONG_MAX)!=pass ) ) continue;
371 :
372 0 : if( need_xdp ) {
373 0 : if( FD_UNLIKELY( strcmp( tile->name, "net" ) ) ) {
374 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
375 : /* close XDP related file descriptors */
376 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 ) ));
377 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 ) ));
378 0 : }
379 0 : } else {
380 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
381 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 ) ));
382 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 ) ));
383 0 : }
384 0 : }
385 0 : }
386 :
387 0 : if( FD_LIKELY( config->is_firedancer ) ) {
388 0 : int tile_uses_accdb = 0;
389 0 : int tile_uses_accdb_ro = 0;
390 0 : for( ulong i=0UL; i<tile->uses_obj_cnt; i++ ) {
391 0 : fd_topo_obj_t const * obj = &config->topo.objs[ tile->uses_obj_id[ i ] ];
392 0 : if( FD_UNLIKELY( !strcmp( obj->name, "accdb" ) ) ) {
393 0 : if( FD_UNLIKELY( tile->uses_obj_mode[ i ]==FD_SHMEM_JOIN_MODE_READ_ONLY ) ) tile_uses_accdb_ro = 1;
394 0 : else tile_uses_accdb = 1;
395 0 : break;
396 0 : }
397 0 : }
398 :
399 : /* The gui joins the accdb shmem read-only (for partition stats)
400 : but never reads account data from the on-disk file, so it does
401 : not need the accounts.db fd. Withhold it to keep the gui at
402 : least privilege. */
403 0 : if( FD_UNLIKELY( !strcmp( tile->name, "gui" ) ) ) tile_uses_accdb_ro = 0;
404 :
405 : /* snapwr writes accdb pwrite()s without joining accdb shmem, so
406 : it needs the RW fd despite not appearing as an accdb obj user
407 : in the topology. */
408 0 : if( FD_UNLIKELY( tile_uses_accdb || !strcmp( tile->name, "snapwr" ) ) ) {
409 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 ) ));
410 0 : } else {
411 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 ) ));
412 0 : }
413 :
414 0 : if( FD_UNLIKELY( tile_uses_accdb_ro ) ) {
415 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 ) ));
416 0 : } else {
417 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 ) ));
418 0 : }
419 0 : }
420 :
421 0 : int pipefd[ 2 ];
422 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
423 0 : fds[ child_cnt ] = (struct pollfd){ .fd = pipefd[ 0 ], .events = 0 };
424 0 : child_pids[ child_cnt ] = execve_tile( config->name, tile, floating_cpu_set, save_priority, config_memfd, pipefd[ 1 ], &spawn_cg );
425 0 : child_idxs[ child_cnt ] = i;
426 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
427 0 : strncpy( child_names[ child_cnt ], tile->name, 32 );
428 0 : child_cnt++;
429 0 : }
430 0 : }
431 :
432 0 : leave_isolation_cgroup( &spawn_cg );
433 :
434 : /* Obtain the actual grandchild PID from the pipe */
435 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
436 0 : if( FD_UNLIKELY( actual_pids[ i ]!=ULONG_MAX ) ) continue;
437 0 : FD_TEST( 8UL==read( fds[ i ].fd, &actual_pids[ i ], 8UL ) );
438 0 : }
439 :
440 0 : if( FD_UNLIKELY( -1==setpriority( PRIO_PROCESS, 0, save_priority ) ) ) FD_LOG_ERR(( "setpriority() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
441 0 : if( FD_UNLIKELY( fd_cpuset_setaffinity( 0, floating_cpu_set ) ) )
442 0 : FD_LOG_ERR(( "fd_cpuset_setaffinity failed (%i-%s)", errno, fd_io_strerror( errno ) ));
443 :
444 0 : if( FD_UNLIKELY( close( config_memfd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
445 0 : if( need_xdp ) {
446 0 : for( uint i=0U; i<xdp_fds_cnt; i++ ) {
447 0 : if( FD_UNLIKELY( close( xdp_fds[i].xsk_map_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
448 0 : if( FD_UNLIKELY( close( xdp_fds[i].prog_link_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
449 0 : }
450 0 : }
451 :
452 0 : if( FD_LIKELY( config->is_firedancer ) ) {
453 0 : if( FD_UNLIKELY( -1==close( FD_ACCDB_FD_RW ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
454 0 : if( FD_UNLIKELY( -1==close( FD_ACCDB_FD_RO ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
455 0 : }
456 :
457 0 : int allow_fds[ 4+FD_TOPO_MAX_TILES ];
458 0 : ulong allow_fds_cnt = 0;
459 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
460 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
461 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
462 0 : allow_fds[ allow_fds_cnt++ ] = args->pipefd[ 1 ]; /* write end of main pipe */
463 0 : for( ulong i=0UL; i<child_cnt; i++ )
464 0 : allow_fds[ allow_fds_cnt++ ] = fds[ i ].fd; /* read end of child pipes */
465 :
466 0 : struct sock_filter seccomp_filter[ 128UL ];
467 0 : unsigned int instr_cnt;
468 : #if defined(__aarch64__)
469 : populate_sock_filter_policy_pidns_arm64( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd() );
470 : instr_cnt = sock_filter_policy_pidns_arm64_instr_cnt;
471 : #else
472 0 : populate_sock_filter_policy_pidns( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd() );
473 0 : instr_cnt = sock_filter_policy_pidns_instr_cnt;
474 0 : #endif
475 :
476 0 : if( FD_LIKELY( config->development.sandbox ) ) {
477 0 : fd_sandbox_enter( config->uid,
478 0 : config->gid,
479 0 : 0,
480 0 : 0,
481 0 : 0,
482 0 : 0,
483 0 : 0,
484 0 : 1UL+child_cnt, /* RLIMIT_NOFILE needs to be set to the nfds argument of poll() */
485 0 : 0UL,
486 0 : 0UL,
487 0 : 0UL,
488 0 : allow_fds_cnt,
489 0 : allow_fds,
490 0 : instr_cnt,
491 0 : seccomp_filter );
492 0 : } else {
493 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
494 0 : }
495 :
496 : /* Reap child process PIDs so they don't show up in `ps` etc. All of
497 : these children should have exited immediately after clone(2)'ing
498 : another child with a huge page based stack. */
499 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
500 0 : int wstatus;
501 0 : int exited_pid = wait4( child_pids[ i ], &wstatus, (int)__WALL, NULL );
502 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
503 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s) %lu %hu", errno, fd_io_strerror( errno ), i, fds[i].revents ));
504 0 : } else if( FD_UNLIKELY( child_pids[ i ]!=exited_pid ) ) {
505 0 : FD_LOG_ERR(( "pidns wait4() returned unexpected pid %d %d", child_pids[ i ], exited_pid ));
506 0 : } else if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
507 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 ) ) ));
508 0 : fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
509 0 : }
510 0 : if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) ) {
511 0 : FD_LOG_ERR_NOEXIT(( "tile %lu (%s) exited while booting with code %d", i, child_names[ i ], WEXITSTATUS( wstatus ) ));
512 0 : fd_sys_util_exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
513 0 : }
514 0 : }
515 :
516 0 : fds[ child_cnt ] = (struct pollfd){ .fd = args->pipefd[ 1 ], .events = 0 };
517 0 : strncpy( child_names[ child_cnt ], "parent", 32UL );
518 0 : child_idxs[ child_cnt ] = ULONG_MAX;
519 :
520 : /* We are now the init process of the pid namespace. If the init
521 : process dies, all children are terminated. If any child dies, we
522 : terminate the init process, which will cause the kernel to
523 : terminate all other children bringing all of our processes down as
524 : a group. The parent process will also die if this process dies,
525 : due to getting SIGHUP on the pipe. */
526 0 : while( 1 ) {
527 0 : if( FD_UNLIKELY( -1==poll( fds, 1UL+child_cnt, (int)-1 ) ) ) FD_LOG_ERR(( "poll() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
528 :
529 : /* Parent process died, probably SIGINT, exit gracefully. */
530 0 : if( FD_UNLIKELY( fds[ child_cnt ].revents ) ) fd_sys_util_exit_group( 0 );
531 :
532 : /* Child process died, reap it to figure out exit code. */
533 0 : int wstatus;
534 0 : int exited_pid = wait4( -1, &wstatus, (int)__WALL | (int)WNOHANG, NULL );
535 0 : if( FD_UNLIKELY( -1==exited_pid ) ) {
536 0 : FD_LOG_ERR(( "pidns wait4() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
537 0 : } else if( FD_UNLIKELY( !exited_pid ) ) {
538 : /* Spurious wakeup, no child actually dead yet. */
539 0 : continue;
540 0 : }
541 :
542 : /* Now find the tile corresponding to that PID */
543 0 : FD_TEST( exited_pid>0 );
544 0 : int found = 0;
545 0 : for( ulong i=0UL; i<child_cnt; i++ ) {
546 0 : if( FD_LIKELY( actual_pids[ i ]!=(ulong)exited_pid ) ) continue;
547 :
548 0 : found = 1;
549 0 : fds[ i ].fd = -1; /* Don't poll on this tile anymore */
550 :
551 0 : char * tile_name = child_names[ i ];
552 0 : ulong tile_idx = child_idxs[ i ];
553 0 : ulong tile_id = config->topo.tiles[ tile_idx ].kind_id;
554 :
555 0 : if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) {
556 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() ));
557 0 : fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
558 0 : } else {
559 0 : int exit_code = WEXITSTATUS( wstatus );
560 0 : if( FD_LIKELY( !exit_code && tile_idx!=ULONG_MAX && config->topo.tiles[ tile_idx ].allow_shutdown ) ) {
561 0 : found = 1;
562 0 : FD_LOG_INFO(( "tile %s:%lu exited gracefully with code %d", tile_name, tile_id, exit_code ));
563 0 : } else {
564 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 ));
565 0 : fd_sys_util_exit_group( exit_code ? exit_code : 1 );
566 0 : }
567 0 : }
568 0 : }
569 :
570 0 : if( FD_UNLIKELY( !found ) ) FD_LOG_ERR(( "wait4() returned unexpected pid %d", exited_pid ));
571 0 : }
572 :
573 0 : return 0;
574 0 : }
575 :
576 : int
577 : clone_firedancer( config_t const * config,
578 : int close_fd,
579 0 : int * out_pipe ) {
580 : /* This pipe is here just so that the child process knows when the
581 : parent has died (it will get a HUP). */
582 0 : int pipefd[2];
583 0 : if( FD_UNLIKELY( pipe2( pipefd, O_CLOEXEC | O_NONBLOCK ) ) ) FD_LOG_ERR(( "pipe2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
584 :
585 : /* clone into a pid namespace */
586 0 : int flags = config->development.sandbox ? CLONE_NEWPID : 0;
587 0 : struct pidns_clone_args args = { .config = config, .closefd = close_fd, .pipefd = pipefd, };
588 :
589 0 : void * stack = create_clone_stack();
590 :
591 0 : int pid_namespace = clone( main_pid_namespace, (uchar *)stack + FD_TILE_PRIVATE_STACK_SZ, flags, &args );
592 0 : if( FD_UNLIKELY( pid_namespace<0 ) ) FD_LOG_ERR(( "clone() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
593 :
594 0 : if( FD_UNLIKELY( close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
595 :
596 0 : *out_pipe = pipefd[ 0 ];
597 0 : return pid_namespace;
598 0 : }
599 :
600 : static void
601 : workspace_path( config_t const * config,
602 : fd_topo_wksp_t const * wksp,
603 0 : char out[ PATH_MAX ] ) {
604 0 : char const * mount_path;
605 0 : switch( wksp->page_sz ) {
606 0 : case FD_SHMEM_HUGE_PAGE_SZ:
607 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
608 0 : break;
609 0 : case FD_SHMEM_GIGANTIC_PAGE_SZ:
610 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
611 0 : break;
612 0 : case FD_SHMEM_NORMAL_PAGE_SZ:
613 0 : mount_path = config->hugetlbfs.normal_page_mount_path;
614 0 : break;
615 0 : default:
616 0 : FD_LOG_ERR(( "invalid page size %lu", wksp->page_sz ));
617 0 : }
618 :
619 0 : FD_TEST( fd_cstr_printf_check( out, PATH_MAX, NULL, "%s/%s_%s.wksp", mount_path, config->name, wksp->name ) );
620 0 : }
621 :
622 : static void
623 : warn_unknown_files( config_t const * config,
624 0 : ulong mount_type ) {
625 0 : char const * mount_path;
626 0 : switch( mount_type ) {
627 0 : case 0UL:
628 0 : mount_path = config->hugetlbfs.huge_page_mount_path;
629 0 : break;
630 0 : case 1UL:
631 0 : mount_path = config->hugetlbfs.gigantic_page_mount_path;
632 0 : break;
633 0 : default:
634 0 : FD_LOG_ERR(( "invalid mount type %lu", mount_type ));
635 0 : }
636 :
637 : /* Check if there are any files in mount_path */
638 0 : DIR * dir = opendir( mount_path );
639 0 : if( FD_UNLIKELY( !dir ) ) {
640 0 : if( FD_UNLIKELY( errno!=ENOENT ) ) FD_LOG_ERR(( "error opening `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
641 0 : return;
642 0 : }
643 :
644 0 : struct dirent * entry;
645 0 : for(;;) {
646 0 : errno = 0;
647 0 : entry = readdir( dir );
648 0 : if( FD_UNLIKELY( !entry ) ) break;
649 0 : if( FD_UNLIKELY( !strcmp( entry->d_name, ".") || !strcmp( entry->d_name, ".." ) ) ) continue;
650 :
651 0 : char entry_path[ PATH_MAX ];
652 0 : FD_TEST( fd_cstr_printf_check( entry_path, PATH_MAX, NULL, "%s/%s", mount_path, entry->d_name ));
653 :
654 0 : int known_file = 0;
655 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
656 0 : fd_topo_wksp_t const * wksp = &config->topo.workspaces[ i ];
657 :
658 0 : char expected_path[ PATH_MAX ];
659 0 : workspace_path( config, wksp, expected_path );
660 :
661 0 : if( !strcmp( entry_path, expected_path ) ) {
662 0 : known_file = 1;
663 0 : break;
664 0 : }
665 0 : }
666 :
667 0 : if( mount_type==0UL ) {
668 0 : for( ulong i=0UL; i<config->topo.tile_cnt; i++ ) {
669 0 : fd_topo_tile_t const * tile = &config->topo.tiles [ i ];
670 :
671 0 : char expected_path[ PATH_MAX ];
672 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 ) );
673 :
674 0 : if( !strcmp( entry_path, expected_path ) ) {
675 0 : known_file = 1;
676 0 : break;
677 0 : }
678 0 : }
679 0 : }
680 :
681 0 : if( FD_UNLIKELY( !known_file ) ) FD_LOG_WARNING(( "unknown file `%s` found in `%s`", entry->d_name, mount_path ));
682 0 : }
683 :
684 0 : if( FD_UNLIKELY( errno ) ) FD_LOG_ERR(( "error reading dir `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
685 0 : if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "error closing `%s` (%i-%s)", mount_path, errno, fd_io_strerror( errno ) ));
686 0 : }
687 :
688 : void
689 0 : initialize_workspaces( config_t * config ) {
690 : /* Switch to non-root uid/gid for workspace creation. Permissions
691 : checks are still done as the current user. */
692 0 : uint gid = getgid();
693 0 : uint uid = getuid();
694 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
695 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
696 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
697 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
698 :
699 0 : for( ulong i=0UL; i<config->topo.wksp_cnt; i++ ) {
700 0 : fd_topo_wksp_t * wksp = &config->topo.workspaces[ i ];
701 :
702 0 : char path[ PATH_MAX ];
703 0 : workspace_path( config, wksp, path );
704 :
705 0 : struct stat st;
706 0 : int result = stat( path, &st );
707 :
708 0 : int update_existing;
709 0 : if( FD_UNLIKELY( !result && config->is_live_cluster && !config->is_dev ) ) {
710 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 ) ));
711 0 : update_existing = 0;
712 0 : } else if( FD_UNLIKELY( !result ) ) {
713 : /* Creating all of the workspaces is very expensive because the
714 : kernel has to zero out all of the pages. There can be tens or
715 : hundreds of gigabytes of zeroing to do.
716 :
717 : What would be really nice is if the kernel let us create huge
718 : pages without zeroing them, but it's not possible. The
719 : ftruncate and fallocate calls do not support this type of
720 : resize with the hugetlbfs filesystem.
721 :
722 : Instead.. to prevent repeatedly doing this zeroing every time
723 : we start the validator, we have a small hack here to re-use the
724 : workspace files if they exist. */
725 0 : update_existing = 1;
726 0 : } else if( FD_LIKELY( result && errno==ENOENT ) ) {
727 0 : update_existing = 0;
728 0 : } else {
729 0 : FD_LOG_ERR(( "stat failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
730 0 : }
731 :
732 0 : if( FD_UNLIKELY( -1==fd_topo_create_workspace( &config->topo, wksp, update_existing ) ) ) {
733 0 : FD_TEST( errno==ENOMEM );
734 :
735 0 : warn_unknown_files( config, wksp->page_sz!=FD_SHMEM_HUGE_PAGE_SZ );
736 :
737 0 : char path[ PATH_MAX ];
738 0 : workspace_path( config, wksp, path );
739 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create workspace `%s` at `%s` "
740 0 : "with %lu %s pages. Firedancer reserves enough memory for all of its workspaces "
741 0 : "during the `hugetlbfs` configure step, so it is likely you have unknown files "
742 0 : "left over in this directory which are consuming memory, or another program on "
743 0 : "the system is using pages from the same mount.",
744 0 : wksp->name, path, wksp->page_cnt, fd_shmem_page_sz_to_cstr( wksp->page_sz ) ));
745 0 : }
746 0 : fd_topo_join_workspace( &config->topo, wksp, FD_SHMEM_JOIN_MODE_READ_WRITE, 0 );
747 0 : fd_topo_wksp_new( &config->topo, wksp, CALLBACKS );
748 0 : fd_topo_leave_workspace( &config->topo, wksp );
749 0 : }
750 :
751 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
752 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
753 0 : }
754 :
755 : void
756 0 : initialize_stacks( config_t const * config ) {
757 : # if FD_HAS_MSAN
758 : /* MSan calls an external symbolizer using fork() on crashes, which is
759 : incompatible with Firedancer's MAP_SHARED stacks. */
760 : (void)config;
761 : return;
762 : # endif
763 :
764 : /* Switch to non-root uid/gid for workspace creation. Permissions
765 : checks are still done as the current user. */
766 0 : uint gid = getgid();
767 0 : uint uid = getuid();
768 0 : if( FD_LIKELY( gid!=config->gid && -1==setegid( config->gid ) ) )
769 0 : FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
770 0 : if( FD_LIKELY( uid!=config->uid && -1==seteuid( config->uid ) ) )
771 0 : FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
772 :
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 :
776 0 : char path[ PATH_MAX ];
777 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 ) );
778 :
779 0 : struct stat st;
780 0 : int result = stat( path, &st );
781 :
782 0 : int update_existing;
783 0 : if( FD_UNLIKELY( !result && config->is_live_cluster ) ) {
784 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 ) ));
785 0 : update_existing = 0;
786 0 : } else if( FD_UNLIKELY( !result ) ) {
787 : /* See above note about zeroing out pages. */
788 0 : update_existing = 1;
789 0 : } else if( FD_LIKELY( result && errno==ENOENT ) ) {
790 0 : update_existing = 0;
791 0 : } else {
792 0 : FD_LOG_ERR(( "stat failed when trying to create workspace `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) ));
793 0 : }
794 :
795 : /* TODO: Use a better CPU idx for the stack if tile is floating */
796 0 : ulong stack_cpu_idx = 0UL;
797 0 : if( FD_LIKELY( tile->cpu_idx<65535UL ) ) stack_cpu_idx = tile->cpu_idx;
798 :
799 0 : char name[ PATH_MAX ];
800 0 : FD_TEST( fd_cstr_printf_check( name, PATH_MAX, NULL, "%s_stack_%s%lu", config->name, tile->name, tile->kind_id ) );
801 :
802 0 : ulong sub_page_cnt[ 1 ] = { 6 };
803 0 : ulong sub_cpu_idx [ 1 ] = { stack_cpu_idx };
804 0 : int err;
805 0 : if( FD_UNLIKELY( update_existing ) ) {
806 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 */
807 0 : } else {
808 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 */
809 0 : }
810 0 : if( FD_UNLIKELY( err && errno==ENOMEM ) ) {
811 0 : warn_unknown_files( config, 0UL );
812 :
813 0 : char path[ PATH_MAX ];
814 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 ) );
815 0 : FD_LOG_ERR(( "ENOMEM-Out of memory when trying to create huge page stack for tile `%s` at `%s`. "
816 0 : "Firedancer reserves enough memory for all of its stacks during the `hugetlbfs` configure "
817 0 : "step, so it is likely you have unknown files left over in this directory which are "
818 0 : "consuming memory, or another program on the system is using pages from the same mount.",
819 0 : tile->name, path ));
820 0 : } else if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "fd_shmem_create_multi failed" ));
821 0 : }
822 :
823 0 : if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
824 0 : if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
825 0 : }
826 :
827 : void
828 0 : fdctl_check_configure( config_t const * config ) {
829 0 : configure_result_t check = fd_cfg_stage_hugetlbfs.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
830 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
831 0 : FD_LOG_ERR(( "Huge pages are not configured correctly: %s. You can run `%s configure init hugetlbfs` "
832 0 : "to create the mounts correctly. This must be done after every system restart before running "
833 0 : "Firedancer.", check.message, FD_BINARY_NAME ));
834 :
835 0 : if( FD_LIKELY( 0==strcmp( config->net.provider, "xdp" ) ) ) {
836 0 : if( fd_cfg_stage_bonding.enabled( config ) ) {
837 0 : check = fd_cfg_stage_bonding.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
838 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
839 0 : FD_LOG_ERR(( "Bonded network device is not configured correctly: %s. You can run `%s configure init bonding` "
840 0 : "to configure the bonding driver.", check.message, FD_BINARY_NAME ));
841 0 : }
842 :
843 0 : check = fd_cfg_stage_ethtool_channels.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
844 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
845 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-channels` to set the number of channels on the "
846 0 : "network device correctly.", check.message, FD_BINARY_NAME ));
847 :
848 0 : check = fd_cfg_stage_ethtool_offloads.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
849 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
850 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-offloads` to disable features "
851 0 : "as required.", check.message, FD_BINARY_NAME ));
852 :
853 0 : check = fd_cfg_stage_ethtool_loopback.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
854 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
855 0 : FD_LOG_ERR(( "Network %s. You can run `%s configure init ethtool-loopback` to disable tx-udp-segmentation "
856 0 : "on the loopback device.", check.message, FD_BINARY_NAME ));
857 0 : }
858 :
859 0 : check = fd_cfg_stage_sysctl.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
860 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
861 0 : FD_LOG_ERR(( "Kernel parameters are not configured correctly: %s. You can run `%s configure init sysctl` "
862 0 : "to set kernel parameters correctly.", check.message, FD_BINARY_NAME ));
863 :
864 : /* hyperthreads, nohz-full and rcu-nocbs are check-only stages: they
865 : emit warnings themselves and always return OK, so there is no
866 : result to act on (and no init to point the operator at). */
867 0 : (void)fd_cfg_stage_hyperthreads.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
868 0 : (void)fd_cfg_stage_nohz_full.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
869 0 : (void)fd_cfg_stage_rcu_nocbs.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
870 :
871 : /* kworkers and cpuset are optional (but recommended) hardening: an
872 : unconfigured stage only warns. A PARTIALLY_CONFIGURED cpuset is
873 : fatal however: the isolation cgroup exists but covers the wrong
874 : CPUs (e.g. stale from a previous [layout.affinity]), and the tile
875 : launcher would join it and then fail to pin with a confusing
876 : EINVAL. Fail up front with the fix instead. */
877 0 : if( FD_UNLIKELY( fd_cfg_stage_kworkers.enabled( config ) ) ) {
878 0 : check = fd_cfg_stage_kworkers.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
879 0 : if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
880 0 : FD_LOG_WARNING(( "Kernel workqueues may steal CPU time from Firedancer tiles: %s. For lower jitter, run "
881 0 : "`%s configure init kworkers`.", check.message, FD_BINARY_NAME ));
882 0 : }
883 :
884 0 : if( FD_LIKELY( fd_cfg_stage_cpuset.enabled( config ) ) ) {
885 0 : check = fd_cfg_stage_cpuset.check( config, FD_CONFIGURE_CHECK_TYPE_RUN );
886 0 : if( FD_UNLIKELY( check.result==CONFIGURE_PARTIALLY_CONFIGURED ) )
887 0 : FD_LOG_ERR(( "The CPU isolation cgroup exists but does not match the topology: %s. Tiles would fail to pin "
888 0 : "to their CPUs. Run `%s configure init cpuset` to fix it, or `%s configure fini cpuset` to "
889 0 : "remove it.", check.message, FD_BINARY_NAME, FD_BINARY_NAME ));
890 0 : else if( FD_UNLIKELY( check.result!=CONFIGURE_OK ) )
891 0 : FD_LOG_WARNING(( "Firedancer tile CPUs are not isolated from other processes: %s. For lower jitter, run "
892 0 : "`%s configure init cpuset`.", check.message, FD_BINARY_NAME ));
893 0 : }
894 0 : }
895 :
896 : void
897 : run_firedancer_init( config_t * config,
898 : int init_workspaces,
899 0 : int check_configure ) {
900 0 : struct stat st;
901 0 : int err = stat( config->paths.identity_key, &st );
902 0 : if( FD_UNLIKELY( -1==err && errno==ENOENT ) ) FD_LOG_ERR(( "[consensus.identity_path] 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 ));
903 0 : else if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "could not stat [consensus.identity_path] `%s` (%i-%s)", config->paths.identity_key, errno, fd_io_strerror( errno ) ));
904 :
905 0 : if( FD_UNLIKELY( !config->is_firedancer ) ) {
906 0 : for( ulong i=0UL; i<config->frankendancer.paths.authorized_voter_paths_cnt; i++ ) {
907 0 : err = stat( config->frankendancer.paths.authorized_voter_paths[ i ], &st );
908 0 : if( FD_UNLIKELY( -1==err && errno==ENOENT ) ) FD_LOG_ERR(( "[consensus.authorized_voter_paths] key does not exist `%s`", config->frankendancer.paths.authorized_voter_paths[ i ] ));
909 0 : else if( FD_UNLIKELY( -1==err ) ) FD_LOG_ERR(( "could not stat [consensus.authorized_voter_paths] `%s` (%i-%s)", config->frankendancer.paths.authorized_voter_paths[ i ], errno, fd_io_strerror( errno ) ));
910 0 : }
911 0 : }
912 :
913 : /* FIXME: fdctl_check_configure unconditionally checks for network
914 : stack prerequisites even if the command being run does not
915 : require networking. Hack around that here for now. */
916 0 : if( check_configure ) fdctl_check_configure( config );
917 0 : if( FD_LIKELY( init_workspaces ) ) initialize_workspaces( config );
918 0 : initialize_stacks( config );
919 0 : fd_bootinfo_write( config );
920 0 : }
921 :
922 : void
923 0 : initialize_accdb_fd( config_t const * config ) {
924 0 : if( FD_UNLIKELY( !config->is_firedancer ) ) return;
925 :
926 : /* O_TRUNC of a previous run's accounts.db frees all its extents
927 : synchronously. In development skip the truncate to keep reboots
928 : fast. */
929 0 : int oflags = O_RDWR|O_CREAT|O_NOATIME;
930 0 : if( FD_LIKELY( !config->is_dev ) ) oflags |= O_TRUNC;
931 :
932 0 : int accounts_fd = open( config->paths.accounts, oflags, S_IRUSR|S_IWUSR );
933 0 : if( FD_UNLIKELY( -1==accounts_fd ) ) FD_LOG_ERR(( "failed to open accounts.db (%i-%s)", errno, fd_io_strerror( errno ) ));
934 0 : if( FD_UNLIKELY( -1==dup2( accounts_fd, FD_ACCDB_FD_RW ) ) ) FD_LOG_ERR(( "dup2() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
935 0 : if( FD_UNLIKELY( -1==close( accounts_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
936 :
937 : /* Read-only fd for tiles (e.g. rpc) that consume accdb but must not
938 : be able to mutate the on-disk file. Reopen via /proc/self/fd to
939 : guarantee it refers to the same inode as the RW fd, avoiding any
940 : race where the file at the path could be replaced between opens. */
941 0 : char proc_path[ PATH_MAX ];
942 0 : FD_TEST( fd_cstr_printf_check( proc_path, sizeof(proc_path), NULL, "/proc/self/fd/%d", FD_ACCDB_FD_RW ) );
943 0 : int accounts_ro_fd = open( proc_path, O_RDONLY|O_NOATIME );
944 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 ) ));
945 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 ) ));
946 0 : if( FD_UNLIKELY( -1==close( accounts_ro_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
947 0 : }
948 :
949 : /* The boot sequence is a little bit involved...
950 :
951 : A process tree is created that looks like,
952 :
953 : + main
954 : +-- pidns
955 : +-- agave
956 : +-- tile 0
957 : +-- tile 1
958 : ...
959 :
960 : What we want is that if any process in the tree dies, all other
961 : processes will also die. This is done as follows,
962 :
963 : (a) pidns is the init process of a PID namespace, so if it dies the
964 : kernel will terminate the child processes.
965 :
966 : (b) main is the parent of pidns, so it can issue a waitpid() on the
967 : child PID, and when it completes terminate itself.
968 :
969 : (c) pidns is the parent of agave and the tiles, so it could
970 : issue a waitpid() of -1 to wait for any of them to terminate,
971 : but how would it know if main has died?
972 :
973 : (d) main creates a pipe, and passes the write end to pidns. If main
974 : dies, the pipe will be closed, and pidns will get a HUP on the
975 : read end. Then pidns creates a pipe per child and passes the
976 : write end to the child. If any of the children die, the pipe
977 : will be closed, and pidns will get a HUP on the read end.
978 :
979 : Then pidns can call poll() on both the write end of the main
980 : pipe and the read end of all the child pipes. If any of them
981 : raises SIGHUP, then pidns knows that the parent or a child has
982 : died, and it can terminate itself, which due to (a) and (b)
983 : will kill all other processes. */
984 : void
985 : run_firedancer( config_t * config,
986 : int parent_pipefd,
987 0 : int init_workspaces ) {
988 : /* dump the topology we are using to the output log */
989 0 : fd_topo_print_log( 0, &config->topo );
990 :
991 0 : run_firedancer_init( config, init_workspaces, 1 );
992 :
993 0 : if( FD_UNLIKELY( close( 0 ) ) ) FD_LOG_ERR(( "close(0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
994 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 ) ));
995 :
996 0 : int pipefd;
997 0 : pid_namespace = clone_firedancer( config, parent_pipefd, &pipefd );
998 :
999 : /* Print the location of the logfile on SIGINT or SIGTERM, and also
1000 : kill the child. They are connected by a pipe which the child is
1001 : polling so we don't strictly need to kill the child, but its helpful
1002 : to do that before printing the log location line, else it might
1003 : get interleaved due to timing windows in the shutdown. */
1004 0 : install_parent_signals();
1005 :
1006 0 : struct sock_filter seccomp_filter[ 128UL ];
1007 0 : populate_sock_filter_policy_main( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd(), (uint)pid_namespace );
1008 :
1009 0 : int allow_fds[ 4 ];
1010 0 : ulong allow_fds_cnt = 0;
1011 0 : allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */
1012 0 : if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) )
1013 0 : allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile, or maybe stdout */
1014 0 : allow_fds[ allow_fds_cnt++ ] = pipefd; /* read end of main pipe */
1015 0 : if( FD_UNLIKELY( parent_pipefd!=-1 ) )
1016 0 : allow_fds[ allow_fds_cnt++ ] = parent_pipefd; /* write end of parent pipe */
1017 :
1018 0 : if( FD_LIKELY( config->development.sandbox ) ) {
1019 0 : fd_sandbox_enter( config->uid,
1020 0 : config->gid,
1021 0 : 0,
1022 0 : 0,
1023 0 : 0,
1024 0 : 1, /* Keep controlling terminal for main so it can receive Ctrl+C */
1025 0 : 0,
1026 0 : 0UL,
1027 0 : 0UL,
1028 0 : 0UL,
1029 0 : 0UL,
1030 0 : allow_fds_cnt,
1031 0 : allow_fds,
1032 0 : sock_filter_policy_main_instr_cnt,
1033 0 : seccomp_filter );
1034 0 : } else {
1035 0 : fd_sandbox_switch_uid_gid( config->uid, config->gid );
1036 0 : }
1037 :
1038 : /* the only clean way to exit is SIGINT or SIGTERM on this parent process,
1039 : so if wait4() completes, it must be an error */
1040 0 : int wstatus;
1041 0 : if( FD_UNLIKELY( -1==wait4( pid_namespace, &wstatus, (int)__WALL, NULL ) ) )
1042 0 : FD_LOG_ERR(( "main wait4() failed (%i-%s)\nLog at \"%s\"", errno, fd_io_strerror( errno ), fd_log_private_path ));
1043 :
1044 0 : if( FD_UNLIKELY( WIFSIGNALED( wstatus ) ) ) fd_sys_util_exit_group( WTERMSIG( wstatus ) ? WTERMSIG( wstatus ) : 1 );
1045 0 : else fd_sys_util_exit_group( WEXITSTATUS( wstatus ) ? WEXITSTATUS( wstatus ) : 1 );
1046 0 : }
1047 :
1048 : void
1049 : run_cmd_fn( args_t * args FD_PARAM_UNUSED,
1050 0 : config_t * config ) {
1051 0 : #define CHECK_PORT_NON_ZERO( field ) \
1052 0 : if( FD_UNLIKELY( config->field==0 ) ) { \
1053 0 : FD_LOG_ERR(( #field " is not set in your configuration file. Please set it to a non-zero value." )); \
1054 0 : }
1055 :
1056 0 : if( FD_UNLIKELY( !config->gossip.entrypoints_cnt && !config->development.bootstrap ) )
1057 0 : FD_LOG_ERR(( "No entrypoints specified in configuration file under [gossip.entrypoints], but "
1058 0 : "at least one is needed to determine how to connect to the Solana cluster. If "
1059 0 : "you want to start a new cluster in a development environment, use `fddev` instead "
1060 0 : "of `fdctl`. If you want to use an existing genesis, set [development.bootstrap] "
1061 0 : "to \"true\" in the configuration file." ));
1062 :
1063 0 : for( ulong i=0; i<config->gossip.entrypoints_cnt; i++ ) {
1064 0 : if( FD_UNLIKELY( !strcmp( config->gossip.entrypoints[ i ], "" ) ) )
1065 0 : FD_LOG_ERR(( "One of the entrypoints in your configuration file under [gossip.entrypoints] is "
1066 0 : "empty. Please remove the empty entrypoint or set it correctly. "));
1067 0 : }
1068 :
1069 0 : CHECK_PORT_NON_ZERO( gossip.port );
1070 0 : CHECK_PORT_NON_ZERO( tiles.quic.quic_transaction_listen_port );
1071 0 : CHECK_PORT_NON_ZERO( tiles.quic.regular_transaction_listen_port );
1072 0 : CHECK_PORT_NON_ZERO( tiles.shred.shred_listen_port );
1073 0 : CHECK_PORT_NON_ZERO( tiles.metric.prometheus_listen_port );
1074 0 : CHECK_PORT_NON_ZERO( tiles.gui.gui_listen_port );
1075 :
1076 0 : #undef CHECK_PORT_NON_ZERO
1077 :
1078 0 : run_firedancer( config, -1, 1 );
1079 0 : }
1080 :
1081 : static void
1082 0 : run1_args_help( fd_action_help_t * help ) {
1083 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"
1084 0 : "thread pinned to a CPU core that performs one part of the validator's work" );
1085 : fd_action_help_arg( help, "<kind-id>", NULL, "Zero-based index selecting which instance of that tile type to run\n"
1086 0 : "when the topology has more than one" );
1087 0 : fd_action_help_arg( help, "--pipe-fd", "<fd>", "Internal use: file descriptor over which the parent supervisor process\n"
1088 0 : "communicates with this tile (default -1, standalone)" );
1089 0 : }
1090 :
1091 : action_t fd_action_run1 = {
1092 : .name = "run1",
1093 : .args = run1_cmd_args,
1094 : .fn = run1_cmd_fn,
1095 : .perm = NULL,
1096 : .description = "Start up a single Firedancer tile",
1097 : .detail = "Runs one tile of the validator topology in the current process. A tile is a\n"
1098 : "single thread pinned to a CPU core that performs one part of the validator's\n"
1099 : "work. This is primarily an internal command used by `run` to spawn individual\n"
1100 : "tiles; most operators should use `run` instead.",
1101 : .usage = "run1 <tile-name> <kind-id> [OPTIONS]",
1102 : .args_help = run1_args_help,
1103 : };
1104 :
1105 : action_t fd_action_run = {
1106 : .name = "run",
1107 : .args = NULL,
1108 : .fn = run_cmd_fn,
1109 : .require_config = 1,
1110 : .perm = run_cmd_perm,
1111 : .description = "Start up a Firedancer validator",
1112 : .detail = "Boots and runs the full validator described by the configuration file. This\n"
1113 : "is the main command operators use to run Firedancer. It must be started with\n"
1114 : "sufficient privileges to perform boot-time setup, after which it drops\n"
1115 : "privileges to the configured user.",
1116 : .usage = "run [OPTIONS]",
1117 : .permission_err = "insufficient permissions to execute command `%s`. It is recommended "
1118 : "to start Firedancer as the root user, but you can also start it "
1119 : "with the missing capabilities listed above. The program only needs "
1120 : "to start with elevated permissions to do privileged operations at "
1121 : "boot, and will immediately drop permissions and switch to the user "
1122 : "specified in your configuration file once they are complete. Firedancer "
1123 : "will not execute outside of the boot process as root, and will refuse "
1124 : "to start if it cannot drop privileges. Firedancer needs to be started "
1125 : "privileged to configure high performance networking with XDP.",
1126 : };
|