Line data Source code
1 : #include "configure.h"
2 :
3 : #include <errno.h>
4 : #include <sys/stat.h>
5 :
6 : void
7 : configure_cmd_args( int * pargc,
8 : char *** pargv,
9 0 : args_t * args) {
10 0 : char * usage = "usage: configure <init|check|fini> <stage>...";
11 0 : if( FD_UNLIKELY( *pargc < 2 ) ) FD_LOG_ERR(( "%s", usage ));
12 :
13 0 : if( FD_LIKELY( !strcmp( *pargv[ 0 ], "check" ) ) ) args->configure.command = CONFIGURE_CMD_CHECK;
14 0 : else if( FD_LIKELY( !strcmp( *pargv[ 0 ], "init" ) ) ) args->configure.command = CONFIGURE_CMD_INIT;
15 0 : else if( FD_LIKELY( !strcmp( *pargv[ 0 ], "fini" ) ) ) args->configure.command = CONFIGURE_CMD_FINI;
16 0 : else FD_LOG_ERR(( "unrecognized command `%s`, %s", *pargv[0], usage ));
17 :
18 0 : (*pargc)--;
19 0 : (*pargv)++;
20 :
21 0 : for( int i=0; i<*pargc; i++ ) {
22 0 : if( FD_UNLIKELY( !strcmp( (*pargv)[ i ], "all" ) ) ) {
23 0 : (*pargc) -= i + 1;
24 0 : (*pargv) += i + 1;
25 0 : for( int j=0UL; STAGES[ j ]; j++) args->configure.stages[ j ] = STAGES[ j ];
26 0 : return;
27 0 : }
28 0 : }
29 :
30 0 : ulong nstage = 0UL;
31 0 : while( *pargc ) {
32 0 : int found = 0;
33 0 : for( configure_stage_t ** stage = STAGES; *stage; stage++ ) {
34 0 : if( FD_UNLIKELY( !strcmp( (*pargv)[0], (*stage)->name ) ) ) {
35 0 : args->configure.stages[ nstage++ ] = *stage;
36 0 : found = 1;
37 0 : break;
38 0 : }
39 0 : }
40 :
41 0 : if( FD_UNLIKELY( !found ) ) FD_LOG_ERR(( "unknown configure stage: %s", (*pargv)[0] ));
42 :
43 0 : (*pargc)--;
44 0 : (*pargv)++;
45 0 : }
46 0 : return;
47 0 : }
48 :
49 : void
50 : configure_cmd_perm( args_t * args,
51 : fd_cap_chk_t * chk,
52 0 : config_t const * config ) {
53 0 : for( configure_stage_t ** stage = args->configure.stages; *stage; stage++ ) {
54 0 : switch( args->configure.command ) {
55 0 : case CONFIGURE_CMD_INIT: {
56 0 : int enabled = !(*stage)->enabled || (*stage)->enabled( config );
57 0 : if( FD_LIKELY( enabled && (*stage)->check( config, FD_CONFIGURE_CHECK_TYPE_INIT_PERM ).result != CONFIGURE_OK ) )
58 0 : if( FD_LIKELY( (*stage)->init_perm ) ) (*stage)->init_perm( chk, config );
59 0 : break;
60 0 : }
61 0 : case CONFIGURE_CMD_CHECK:
62 0 : break;
63 0 : case CONFIGURE_CMD_FINI: {
64 0 : int enabled = !(*stage)->enabled || (*stage)->enabled( config );
65 0 : if( FD_LIKELY( enabled && (*stage)->check( config, FD_CONFIGURE_CHECK_TYPE_FINI_PERM ).result != CONFIGURE_NOT_CONFIGURED ) )
66 0 : if( FD_LIKELY( (*stage)->fini_perm ) ) (*stage)->fini_perm( chk, config );
67 0 : break;
68 0 : }
69 0 : }
70 0 : }
71 0 : }
72 :
73 : int
74 : configure_stage( configure_stage_t * stage,
75 : configure_cmd_t command,
76 0 : config_t const * config ) {
77 0 : if( FD_UNLIKELY( stage->enabled && !stage->enabled( config ) ) ) {
78 0 : FD_LOG_INFO(( "%s%s%s ... skipping .. not enabled", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
79 0 : return 0;
80 0 : }
81 :
82 0 : switch( command ) {
83 0 : case CONFIGURE_CMD_INIT: {
84 0 : configure_result_t result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_PRE_INIT );
85 0 : if( FD_UNLIKELY( result.result == CONFIGURE_NOT_CONFIGURED ) )
86 0 : FD_LOG_NOTICE(( "%s%s%s ... unconfigured ... %s%s%s", fd_log_style_bold(), stage->name, fd_log_style_normal(), fd_log_style_dim(), result.message, fd_log_style_normal() ));
87 0 : else if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED ) ) {
88 0 : if( FD_LIKELY( stage->fini ) ) {
89 0 : FD_LOG_NOTICE(( "%s%s%s ... undoing ... %s%s%s", fd_log_style_bold(), stage->name, fd_log_style_normal(), fd_log_style_dim(), result.message, fd_log_style_normal() ));
90 0 : stage->fini( config, 1 );
91 0 : } else if( FD_UNLIKELY( !stage->always_recreate ) ) {
92 0 : FD_LOG_ERR(( "%s ... does not support undo but was not valid ... %s", stage->name, result.message ));
93 0 : }
94 :
95 0 : result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_UNDO_INIT );
96 0 : if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED && !stage->always_recreate ) )
97 0 : FD_LOG_ERR(( "%s ... clean was unable to get back to an unconfigured state ... %s", stage->name, result.message ));
98 0 : } else {
99 0 : FD_LOG_INFO(( "%s%s%s ... already valid", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
100 0 : return 0;
101 0 : }
102 :
103 0 : FD_LOG_NOTICE(( "%s%s%s ... configuring", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
104 0 : if( FD_LIKELY( stage->init ) ) stage->init( config );
105 0 : FD_LOG_INFO(( "%s%s%s ... done", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
106 :
107 0 : result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_POST_INIT );
108 0 : if( FD_UNLIKELY( result.result == CONFIGURE_NOT_CONFIGURED ) )
109 0 : FD_LOG_ERR(( "%s ... tried to initialize but didn't do anything ... %s", stage->name, result.message ));
110 0 : else if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED && !stage->always_recreate ) )
111 0 : FD_LOG_ERR(( "%s ... tried to initialize but was still unconfigured ... %s", stage->name, result.message ));
112 0 : break;
113 0 : }
114 0 : case CONFIGURE_CMD_CHECK: {
115 0 : configure_result_t result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_CHECK );
116 0 : if( FD_UNLIKELY( result.result == CONFIGURE_NOT_CONFIGURED ) ) {
117 0 : FD_LOG_WARNING(( "%s%s%s ... not configured ... %s%s%s", fd_log_style_bold(), stage->name, fd_log_style_normal(), fd_log_style_dim(), result.message, fd_log_style_normal() ));
118 0 : return 1;
119 0 : } else if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED ) ) {
120 0 : if( FD_UNLIKELY( !stage->always_recreate ) ) {
121 0 : FD_LOG_WARNING(( "%s%s%s ... invalid ... %s%s%s", fd_log_style_bold(), stage->name, fd_log_style_normal(), fd_log_style_dim(), result.message, fd_log_style_normal() ));
122 0 : return 1;
123 0 : } else {
124 0 : FD_LOG_NOTICE(( "%s%s%s ... not configured ... must always be recreated", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
125 0 : }
126 0 : }
127 0 : break;
128 0 : }
129 0 : case CONFIGURE_CMD_FINI: {
130 0 : configure_result_t result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_PRE_FINI );
131 :
132 0 : if( FD_UNLIKELY( result.result == CONFIGURE_NOT_CONFIGURED ) ) {
133 0 : FD_LOG_NOTICE(( "%s%s%s ... not configured ... %s%s%s", fd_log_style_bold(), stage->name, fd_log_style_normal(), fd_log_style_dim(), result.message, fd_log_style_normal() ));
134 0 : return 0;
135 0 : } else if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED && !stage->always_recreate && !stage->fini ) ) {
136 0 : FD_LOG_ERR(( "%s ... not valid ... %s", stage->name, result.message ));
137 0 : }
138 :
139 0 : FD_LOG_NOTICE(( "%s%s%s ... finishing", fd_log_style_bold(), stage->name, fd_log_style_normal() ));
140 0 : int fini_done = 0;
141 0 : if( FD_LIKELY( stage->fini ) ) fini_done = stage->fini( config, 0 );
142 :
143 0 : result = stage->check( config, FD_CONFIGURE_CHECK_TYPE_POST_FINI );
144 0 : if( FD_UNLIKELY( result.result == CONFIGURE_OK && stage->init && fini_done ) ) {
145 : /* if the fini step does nothing, it's fine if it's fully configured
146 : after being undone */
147 0 : FD_LOG_ERR(( "%s ... not undone", stage->name ));
148 0 : } else if( FD_UNLIKELY( result.result == CONFIGURE_PARTIALLY_CONFIGURED && !stage->always_recreate ) ) {
149 0 : FD_LOG_ERR(( "%s ... invalid ... %s", stage->name, result.message ));
150 0 : }
151 0 : break;
152 0 : }
153 0 : }
154 :
155 0 : return 0;
156 0 : }
157 :
158 : void
159 : configure_cmd_fn( args_t * args,
160 0 : config_t * config ) {
161 0 : int error = 0;
162 :
163 0 : if( FD_LIKELY( (configure_cmd_t)args->configure.command != CONFIGURE_CMD_FINI ) ) {
164 0 : for( configure_stage_t ** stage = args->configure.stages; *stage; stage++ ) {
165 0 : if( FD_UNLIKELY( configure_stage( *stage, (configure_cmd_t)args->configure.command, config ) ) ) error = 1;
166 0 : }
167 0 : } else {
168 0 : ulong i;
169 0 : for( i=0; args->configure.stages[ i ]; i++ ) ;
170 0 : if( FD_LIKELY( i > 0 ) ) {
171 0 : for( ulong j=0; j<i; j++ ) {
172 0 : if( FD_UNLIKELY( configure_stage( args->configure.stages[ i-1-j ], (configure_cmd_t)args->configure.command, config ) ) ) error = 1;
173 0 : }
174 0 : }
175 0 : }
176 :
177 :
178 0 : if( FD_UNLIKELY( error ) ) FD_LOG_ERR(( "failed to configure some stages" ));
179 0 : }
180 :
181 : static configure_result_t
182 : check_path( const char * path,
183 : uint expected_uid,
184 : uint expected_gid,
185 : uint expected_mode,
186 0 : int expected_dir ) {
187 0 : struct stat st;
188 0 : if( FD_UNLIKELY( stat( path, &st ) ) ) {
189 0 : if( FD_LIKELY( errno == ENOENT ) ) PARTIALLY_CONFIGURED( "path `%s` does not exist", path );
190 0 : PARTIALLY_CONFIGURED( "failed to stat `%s` (%i-%s)", path, errno, fd_io_strerror( errno ) );
191 0 : }
192 0 : if( FD_UNLIKELY( expected_dir && !S_ISDIR( st.st_mode ) ) )
193 0 : PARTIALLY_CONFIGURED( "path `%s` is a file, not a directory", path );
194 0 : if( FD_UNLIKELY( !expected_dir && S_ISDIR( st.st_mode ) ) )
195 0 : PARTIALLY_CONFIGURED( "path `%s` is a directory, not a file", path );
196 :
197 0 : if( FD_UNLIKELY( st.st_uid != expected_uid ) )
198 0 : PARTIALLY_CONFIGURED( "path `%s` has uid %u, expected %u", path, st.st_uid, expected_uid );
199 0 : if( FD_UNLIKELY( st.st_gid != expected_gid ) )
200 0 : PARTIALLY_CONFIGURED( "path `%s` has gid %u, expected %u", path, st.st_gid, expected_gid );
201 0 : if( FD_UNLIKELY( st.st_mode != expected_mode ) )
202 0 : PARTIALLY_CONFIGURED( "path `%s` has mode %o, expected %o", path, st.st_mode, expected_mode );
203 :
204 0 : CONFIGURE_OK();
205 0 : }
206 :
207 : configure_result_t
208 : check_dir( const char * path,
209 : uint uid,
210 : uint gid,
211 0 : uint mode ) {
212 0 : return check_path( path, uid, gid, mode, 1 );
213 0 : }
214 :
215 : configure_result_t
216 : check_file( const char * path,
217 : uint uid,
218 : uint gid,
219 0 : uint mode ) {
220 0 : return check_path( path, uid, gid, mode, 0 );
221 0 : }
222 :
223 : static char const *
224 0 : configure_stage_help( char const * name ) {
225 0 : if( !strcmp( name, "hugetlbfs" ) ) return "mount the huge page filesystems";
226 0 : if( !strcmp( name, "sysctl" ) ) return "apply required kernel sysctl tunables";
227 0 : if( !strcmp( name, "hyperthreads" ) ) return "check sibling hyperthreads are not in use";
228 0 : if( !strcmp( name, "bonding" ) ) return "tune settings on bonded network interfaces";
229 0 : if( !strcmp( name, "ethtool-channels" ) ) return "set the NIC channel (queue) count";
230 0 : if( !strcmp( name, "ethtool-offloads" ) ) return "set the required NIC offload settings";
231 0 : if( !strcmp( name, "ethtool-loopback" ) ) return "disable an incompatible offload on the loopback interface";
232 0 : if( !strcmp( name, "irq-affinity" ) ) return "remove Firedancer tile CPUs from /proc/irq CPU affinity masks";
233 0 : if( !strcmp( name, "irq-balance" ) ) return "remove Firedancer tile CPUs from irqbalance daemon";
234 0 : if( !strcmp( name, "snapshots" ) ) return "prepare the snapshot download directory";
235 0 : if( !strcmp( name, "kill" ) ) return "kill any running validator";
236 0 : if( !strcmp( name, "keys" ) ) return "generate dev identity/vote keypairs";
237 0 : if( !strcmp( name, "genesis" ) ) return "generate a local cluster genesis";
238 0 : if( !strcmp( name, "blockstore" ) ) return "create the genesis block in the ledger blockstore";
239 0 : return NULL;
240 0 : }
241 :
242 : static void
243 0 : configure_args_help( fd_action_help_t * help ) {
244 0 : fd_action_help_arg( help, "init <stage>...", NULL, "Apply the named configuration stages, performing whatever host setup they require" );
245 0 : fd_action_help_arg( help, "check <stage>...", NULL, "Report whether the named configuration stages are already applied, without changing\n"
246 0 : "anything (exits non-zero if any stage is not configured)" );
247 0 : fd_action_help_arg( help, "fini <stage>...", NULL, "Undo the named configuration stages, reverting their host setup" );
248 0 : fd_action_help_arg( help, "all", NULL, "Use in place of <stage>... to apply the command to every known stage\n"
249 0 : "(e.g. `configure init all`)" );
250 :
251 : /* List the stages this binary actually supports (these differ per
252 : binary), padding stage names to a common width so the descriptions
253 : align. */
254 0 : static char stages[ 2048 ];
255 0 : char * p = fd_cstr_init( stages );
256 0 : char * end = stages + sizeof(stages);
257 :
258 0 : ulong name_width = 0UL;
259 0 : for( configure_stage_t ** stage = STAGES; *stage; stage++ ) {
260 0 : ulong len = strlen( (*stage)->name );
261 0 : if( len>name_width ) name_width = len;
262 0 : }
263 :
264 0 : char line[ 256 ];
265 0 : ulong line_len;
266 0 : FD_TEST( fd_cstr_printf_check( line, sizeof(line), &line_len, "One of the configuration stages below:" ) && line_len < (ulong)(end-p) );
267 0 : p = fd_cstr_append_cstr( p, line );
268 :
269 0 : for( configure_stage_t ** stage = STAGES; *stage; stage++ ) {
270 0 : char const * desc = configure_stage_help( (*stage)->name );
271 0 : if( FD_UNLIKELY( !desc ) ) continue; /* no help text for this stage */
272 0 : FD_TEST( fd_cstr_printf_check( line, sizeof(line), &line_len, "\n %-*s %s", (int)name_width, (*stage)->name, desc ) && line_len < (ulong)(end-p) );
273 0 : p = fd_cstr_append_cstr( p, line );
274 0 : }
275 0 : fd_cstr_fini( p );
276 :
277 : fd_action_help_arg( help, "<stage>", NULL, stages );
278 0 : }
279 :
280 : action_t fd_action_configure = {
281 : .name = "configure",
282 : .args = configure_cmd_args,
283 : .fn = configure_cmd_fn,
284 : .perm = configure_cmd_perm,
285 : .description = "Configure the local host so it can run Firedancer correctly",
286 : .detail = "Performs the privileged, host-level setup Firedancer needs before it can run,\n"
287 : "organized into stages (such as mounting huge page filesystems and tuning\n"
288 : "sysctls). Each stage can be applied (init), verified (check), or reverted\n"
289 : "(fini). Typically run once as root before starting the validator.",
290 : .usage = "configure <init|check|fini> <stage>...",
291 : .args_help = configure_args_help,
292 : .permission_err = "insufficient permissions to execute command `%s`. It is recommended "
293 : "to configure Firedancer as the root user. Firedancer configuration requires "
294 : "root because it does privileged operating system actions like mounting huge page filesystems. "
295 : "Configuration is a local action that does not access the network, and the process "
296 : "exits immediately once configuration completes. The user that Firedancer runs "
297 : "as is specified in your configuration file, and although configuration runs as root "
298 : "it will permission the relevant resources for the user in your configuration file, "
299 : "which can be an anonymous maximally restrictive account with no privileges.",
300 : };
|