LCOV - code coverage report
Current view: top level - app/fdctl - main1.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 220 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 7 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #define FD_UNALIGNED_ACCESS_STYLE 0
       3             : 
       4             : #include "fdctl.h"
       5             : #include "topos/topos.h"
       6             : 
       7             : #include "../shared/commands/configure/configure.h"
       8             : #include "../shared/commands/run/run.h"
       9             : #include "../shared/commands/monitor/monitor.h"
      10             : 
      11             : #include <fcntl.h>
      12             : #include <sys/mman.h>
      13             : 
      14             : action_t ACTIONS[] = {
      15             :   { .name = "run",          .args = NULL,                  .fn = run_cmd_fn,          .perm = run_cmd_perm,          .description = "Start up a Firedancer validator" },
      16             :   { .name = "run1",         .args = run1_cmd_args,         .fn = run1_cmd_fn,         .perm = NULL,                  .description = "Start up a single Firedancer tile" },
      17             : #if !FD_HAS_NO_AGAVE
      18             :   { .name = "run-agave",    .args = NULL,                  .fn = run_agave_cmd_fn,    .perm = NULL,                  .description = "Start up the Agave side of a Firedancer validator" },
      19             : #endif
      20             :   { .name = "configure",    .args = configure_cmd_args,    .fn = configure_cmd_fn,    .perm = configure_cmd_perm,    .description = "Configure the local host so it can run Firedancer correctly" },
      21             :   { .name = "monitor",      .args = monitor_cmd_args,      .fn = monitor_cmd_fn,      .perm = monitor_cmd_perm,      .description = "Monitor a locally running Firedancer instance with a terminal GUI" },
      22             :   { .name = "keys",         .args = keys_cmd_args,         .fn = keys_cmd_fn,         .perm = NULL,                  .description = "Generate new keypairs for use with the validator or print a public key" },
      23             :   { .name = "ready",        .args = NULL,                  .fn = ready_cmd_fn,        .perm = NULL,                  .description = "Wait for all tiles to be running" },
      24             :   { .name = "mem",          .args = NULL,                  .fn = mem_cmd_fn,          .perm = NULL,                  .description = "Print workspace memory and tile topology information" },
      25             :   { .name = "netconf",      .args = NULL,                  .fn = netconf_cmd_fn,      .perm = NULL,                  .description = "Print network configuration" },
      26             :   { .name = "set-identity", .args = set_identity_cmd_args, .fn = set_identity_cmd_fn, .perm = set_identity_cmd_perm, .description = "Change the identity of a running validator" },
      27             :   { .name = "help",         .args = NULL,                  .fn = help_cmd_fn,         .perm = NULL,                  .description = "Print this help message" },
      28             :   { .name = "version",      .args = NULL,                  .fn = version_cmd_fn,      .perm = NULL,                  .description = "Show the current software version" },
      29             :   {0}
      30             : };
      31             : 
      32             : struct action_alias {
      33             :   const char * name;
      34             :   const char * alias;
      35             : };
      36             : 
      37             : struct action_alias ALIASES[] = {
      38             :   { .name = "info", .alias = "mem" },
      39             :   { .name = "topo", .alias = "mem" },
      40             : };
      41             : 
      42             : extern int * fd_log_private_shared_lock;
      43             : 
      44             : static void
      45             : copy_config_from_fd( int        config_fd,
      46           0 :                      config_t * config ) {
      47           0 :   uchar * bytes = mmap( NULL, sizeof( config_t ), PROT_READ, MAP_PRIVATE, config_fd, 0 );
      48           0 :   if( FD_UNLIKELY( bytes == MAP_FAILED ) ) FD_LOG_ERR(( "mmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      49           0 :   fd_memcpy( config, bytes, sizeof( config_t ) );
      50           0 :   if( FD_UNLIKELY( munmap( bytes, sizeof( config_t ) ) ) ) FD_LOG_ERR(( "munmap() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      51           0 :   if( FD_UNLIKELY( close( config_fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      52           0 : }
      53             : 
      54             : static int *
      55           0 : map_log_memfd( int log_memfd ) {
      56           0 :   void * shmem = mmap( NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, log_memfd, (off_t)0 );
      57           0 :   if( FD_UNLIKELY( shmem==MAP_FAILED ) ) {
      58           0 :     FD_LOG_ERR(( "mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,memfd,(off_t)0) (%i-%s); ", errno, fd_io_strerror( errno ) ));
      59           0 :   } else {
      60           0 :     if( FD_UNLIKELY( mlock( shmem, 4096 ) ) ) {
      61           0 :       FD_LOG_ERR(( "mlock(%p,4096) (%i-%s); unable to lock log file shared lock in memory\n", shmem, errno, fd_io_strerror( errno ) ));
      62           0 :     }
      63           0 :   }
      64           0 :   return shmem;
      65           0 : }
      66             : 
      67             : /* Try to allocate an anonymous page of memory in a file descriptor
      68             :    (memfd) for fd_log_private_shared_lock such that the log can strictly
      69             :    sequence messages written by clones of the caller made after the
      70             :    caller has finished booting the log.  Must be a file descriptor so
      71             :    we can pass it through `execve` calls. */
      72             : static int
      73           0 : init_log_memfd( void ) {
      74           0 :   int memfd = memfd_create( "fd_log_lock_page", 0U );
      75           0 :   if( FD_UNLIKELY( -1==memfd) ) FD_LOG_ERR(( "memfd_create(\"fd_log_lock_page\",0) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      76           0 :   if( FD_UNLIKELY( -1==ftruncate( memfd, 4096 ) ) ) FD_LOG_ERR(( "ftruncate(memfd,4096) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      77           0 :   return memfd;
      78           0 : }
      79             : 
      80             : static int
      81           0 : should_colorize( void ) {
      82           0 :   char const * cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "COLORTERM", NULL );
      83           0 :   if( cstr && !strcmp( cstr, "truecolor" ) ) return 1;
      84             : 
      85           0 :   cstr = fd_env_strip_cmdline_cstr( NULL, NULL, NULL, "TERM", NULL );
      86           0 :   if( cstr && !strcmp( cstr, "xterm-256color" ) ) return 1;
      87           0 :   return 0;
      88           0 : }
      89             : 
      90             : static void
      91           0 : initialize_numa_assignments( fd_topo_t * topo ) {
      92             :   /* Assign workspaces to NUMA nodes.  The heuristic here is pretty
      93             :      simple for now: workspaces go on the NUMA node of the first
      94             :      tile which maps the largest object in the workspace. */
      95             : 
      96           0 :   for( ulong i=0UL; i<topo->wksp_cnt; i++ ) {
      97           0 :     ulong max_footprint = 0UL;
      98           0 :     ulong max_obj = ULONG_MAX;
      99             : 
     100           0 :     for( ulong j=0UL; j<topo->obj_cnt; j++ ) {
     101           0 :       fd_topo_obj_t * obj = &topo->objs[ j ];
     102           0 :       if( obj->wksp_id!=i ) continue;
     103             : 
     104           0 :       if( FD_UNLIKELY( !max_footprint || obj->footprint>max_footprint ) ) {
     105           0 :         max_footprint = obj->footprint;
     106           0 :         max_obj = j;
     107           0 :       }
     108           0 :     }
     109             : 
     110           0 :     if( FD_UNLIKELY( max_obj==ULONG_MAX ) ) FD_LOG_ERR(( "no object found for workspace %s", topo->workspaces[ i ].name ));
     111             : 
     112           0 :     int found_strict = 0;
     113           0 :     int found_lazy   = 1;
     114           0 :     for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     115           0 :       fd_topo_tile_t * tile = &topo->tiles[ j ];
     116           0 :       if( FD_UNLIKELY( tile->tile_obj_id==max_obj && tile->cpu_idx!=ULONG_MAX ) ) {
     117           0 :         topo->workspaces[ i ].numa_idx = fd_shmem_numa_idx( tile->cpu_idx );
     118           0 :         FD_TEST( topo->workspaces[ i ].numa_idx!=ULONG_MAX );
     119           0 :         found_strict = 1;
     120           0 :         found_lazy = 1;
     121           0 :         break;
     122           0 :       } else if( FD_UNLIKELY( tile->tile_obj_id==max_obj && tile->cpu_idx==ULONG_MAX ) ) {
     123           0 :         topo->workspaces[ i ].numa_idx = 0;
     124           0 :         found_lazy = 1;
     125           0 :         break;
     126           0 :       }
     127           0 :     }
     128             : 
     129           0 :     if( FD_LIKELY( !found_strict ) ) {
     130           0 :       for( ulong j=0UL; j<topo->tile_cnt; j++ ) {
     131           0 :         fd_topo_tile_t * tile = &topo->tiles[ j ];
     132           0 :         for( ulong k=0UL; k<tile->uses_obj_cnt; k++ ) {
     133           0 :           if( FD_LIKELY( tile->uses_obj_id[ k ]==max_obj && tile->cpu_idx!=ULONG_MAX ) ) {
     134           0 :             topo->workspaces[ i ].numa_idx = fd_shmem_numa_idx( tile->cpu_idx );
     135           0 :             FD_TEST( topo->workspaces[ i ].numa_idx!=ULONG_MAX );
     136           0 :             found_lazy = 1;
     137           0 :             break;
     138           0 :           } else if( FD_UNLIKELY( tile->uses_obj_id[ k ]==max_obj ) && tile->cpu_idx==ULONG_MAX ) {
     139           0 :             topo->workspaces[ i ].numa_idx = 0;
     140           0 :             found_lazy = 1;
     141             :             /* Don't break, keep looking -- a tile with a CPU assignment
     142             :                might also use object in which case we want to use that
     143             :                NUMA node. */
     144           0 :           }
     145           0 :         }
     146             : 
     147           0 :         if( FD_UNLIKELY( found_lazy ) ) break;
     148           0 :       }
     149           0 :     }
     150             : 
     151           0 :     if( FD_UNLIKELY( !found_lazy ) ) FD_LOG_ERR(( "no tile uses object %s for workspace %s", topo->objs[ max_obj ].name, topo->workspaces[ i ].name ));
     152           0 :   }
     153           0 : }
     154             : 
     155             : void
     156             : fdctl_boot( int *        pargc,
     157             :             char ***     pargv,
     158             :             config_t   * config,
     159           0 :             char const * log_path ) {
     160           0 :   fd_log_enable_unclean_exit(); /* Don't call atexit handlers on FD_LOG_ERR */
     161           0 :   fd_log_level_core_set( 5 ); /* Don't dump core for FD_LOG_ERR during boot */
     162           0 :   fd_log_colorize_set( should_colorize() ); /* Colorize during boot until we can determine from config */
     163           0 :   fd_log_level_stderr_set( 2 ); /* Only NOTICE and above will be logged during boot until fd_log is initialized */
     164             : 
     165           0 :   int config_fd = fd_env_strip_cmdline_int( pargc, pargv, "--config-fd", NULL, -1 );
     166             : 
     167           0 :   fd_memset( config, 0, sizeof( config_t ) );
     168           0 :   char * thread = "";
     169           0 :   if( FD_UNLIKELY( config_fd >= 0 ) ) {
     170           0 :     copy_config_from_fd( config_fd, config );
     171             :     /* tick_per_ns needs to be synchronized across processes so that
     172             :        they can coordinate on metrics measurement. */
     173           0 :     fd_tempo_set_tick_per_ns( config->tick_per_ns_mu, config->tick_per_ns_sigma );
     174           0 :   } else {
     175           0 :     fdctl_cfg_from_env( pargc, pargv, config );
     176           0 :     fd_topo_initialize( config );
     177           0 :     config->tick_per_ns_mu = fd_tempo_tick_per_ns( &config->tick_per_ns_sigma );
     178           0 :     config->log.lock_fd = init_log_memfd();
     179           0 :     config->log.log_fd  = -1;
     180           0 :     thread = "main";
     181           0 :     if( FD_UNLIKELY( log_path ) )
     182           0 :       strncpy( config->log.path, log_path, sizeof( config->log.path ) - 1 );
     183           0 :   }
     184             : 
     185           0 :   char * shmem_args[ 3 ];
     186             :   /* pass in --shmem-path value from the config */
     187           0 :   shmem_args[ 0 ] = "--shmem-path";
     188           0 :   shmem_args[ 1 ] = config->hugetlbfs.mount_path;
     189           0 :   shmem_args[ 2 ] = NULL;
     190           0 :   char ** argv = shmem_args;
     191           0 :   int     argc = 2;
     192             : 
     193           0 :   int * log_lock = map_log_memfd( config->log.lock_fd );
     194           0 :   ulong pid = fd_sandbox_getpid(); /* Need to read /proc since we might be in a PID namespace now */;
     195             : 
     196           0 :   log_path = config->log.path;
     197           0 :   if( FD_LIKELY( config->log.path[ 0 ]=='\0' ) ) log_path = NULL;
     198             : 
     199             :   /* Switch to the sandbox uid/gid for log file creation, so it's always
     200             :      owned by that user. */
     201             : 
     202           0 :   gid_t gid = getgid();
     203           0 :   uid_t uid = getuid();
     204           0 :   if( FD_LIKELY( !gid && setegid( config->gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     205           0 :   if( FD_LIKELY( !uid && seteuid( config->uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     206             : 
     207           0 :   int boot_silent = config_fd>=0;
     208           0 :   fd_log_private_boot_custom( log_lock,
     209           0 :                               0UL,
     210           0 :                               config->name,
     211           0 :                               0UL,    /* Thread ID will be initialized later */
     212           0 :                               thread, /* Thread will be initialized later */
     213           0 :                               0UL,
     214           0 :                               config->hostname,
     215           0 :                               fd_log_private_cpu_id_default(),
     216           0 :                               NULL,
     217           0 :                               pid,
     218           0 :                               NULL,
     219           0 :                               pid,
     220           0 :                               config->uid,
     221           0 :                               config->user,
     222           0 :                               1,
     223           0 :                               config->log.colorize1,
     224           0 :                               boot_silent ? 2 : config->log.level_logfile1,
     225           0 :                               boot_silent ? 2 : config->log.level_stderr1,
     226           0 :                               boot_silent ? 3 : config->log.level_flush1,
     227           0 :                               5,
     228           0 :                               config->log.log_fd,
     229           0 :                               log_path );
     230             : 
     231           0 :   if( FD_UNLIKELY( seteuid( uid ) ) ) FD_LOG_ERR(( "seteuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     232           0 :   if( FD_UNLIKELY( setegid( gid ) ) ) FD_LOG_ERR(( "setegid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     233             : 
     234           0 :   config->log.log_fd = fd_log_private_logfile_fd();
     235           0 :   fd_shmem_private_boot( &argc, &argv );
     236           0 :   fd_tile_private_boot( 0, NULL );
     237             : 
     238             :   /* Kind of a hack but initializing NUMA config depends on shmem, which
     239             :      depends on logging, which depends on loading the config file, but
     240             :      we would like to do this as part of loading config ... the topo
     241             :      creation needs to be moved out of configuration and happen after
     242             :      boot. */
     243             : 
     244           0 :   if( FD_LIKELY( -1==config_fd ) ) {
     245           0 :     initialize_numa_assignments( &config->topo );
     246           0 :   }
     247             : 
     248           0 :   fd_log_level_logfile_set( config->log.level_logfile1 );
     249           0 :   fd_log_level_stderr_set( config->log.level_stderr1 );
     250           0 :   fd_log_level_flush_set( config->log.level_flush1 );
     251           0 : }
     252             : 
     253             : static config_t config;
     254             : 
     255             : int
     256             : main1( int     argc,
     257           0 :        char ** _argv ) {
     258           0 :   char ** argv = _argv;
     259           0 :   argc--; argv++;
     260             : 
     261             :   /* Short circuit evaluating help and version commands so that we don't
     262             :      need to load and evaluate the entire config file to run them.
     263             :      This is useful for some operators in CI environments where, for
     264             :      example, they want to show the version or validate the produced
     265             :      binary without yet setting up the full TOML. */
     266             : 
     267           0 :   if( FD_UNLIKELY( argc==1 && (!strcmp( argv[ 0 ], "help" ) || !strcmp( argv[ 0 ], "--help" )) ) ) {
     268           0 :     help_cmd_fn( NULL, NULL );
     269           0 :     return 0;
     270           0 :   } else if( FD_UNLIKELY( argc==1 && (!strcmp( argv[ 0 ], "version" ) || !strcmp( argv[ 0 ], "--version" )) ) ) {
     271           0 :     version_cmd_fn( NULL, NULL );
     272           0 :     return 0;
     273           0 :   }
     274             : 
     275           0 :   fdctl_boot( &argc, &argv, &config, NULL );
     276             : 
     277           0 :   if( FD_UNLIKELY( !argc ) ) {
     278           0 :     help_cmd_fn( NULL, &config );
     279           0 :     FD_LOG_ERR(( "no subcommand specified" ));
     280           0 :   }
     281             : 
     282           0 :   const char * command = argv[ 0 ];
     283           0 :   for ( ulong i=0; i <sizeof(ALIASES)/sizeof(ALIASES[ 0 ]); i++ ) {
     284           0 :     if( FD_UNLIKELY( !strcmp( argv[ 0 ], ALIASES[ i ].name ) ) ) {
     285           0 :       command = ALIASES[ i ].alias;
     286           0 :       break;
     287           0 :     }
     288           0 :   }
     289             : 
     290           0 :   action_t * action = NULL;
     291           0 :   for( ulong i=0; ACTIONS[ i ].name; i++ ) {
     292           0 :     if( FD_UNLIKELY( !strcmp( command, ACTIONS[ i ].name ) ) ) {
     293           0 :       action = &ACTIONS[ i ];
     294           0 :       break;
     295           0 :     }
     296           0 :   }
     297           0 :   if( FD_UNLIKELY( !action ) ) {
     298           0 :     help_cmd_fn( NULL, &config );
     299           0 :     FD_LOG_ERR(( "unknown subcommand `%s`", argv[ 0 ] ));
     300           0 :   }
     301             : 
     302           0 :   argc--; argv++;
     303             : 
     304           0 :   args_t args = {0};
     305           0 :   if( FD_LIKELY( action->args ) ) action->args( &argc, &argv, &args );
     306           0 :   if( FD_UNLIKELY( argc ) ) FD_LOG_ERR(( "unknown argument `%s`", argv[ 0 ] ));
     307             : 
     308             :   /* check if we are appropriate permissioned to run the desired command */
     309           0 :   if( FD_LIKELY( action->perm ) ) {
     310           0 :     fd_cap_chk_t * chk = fd_cap_chk_join( fd_cap_chk_new( __builtin_alloca_with_align( fd_cap_chk_footprint(), FD_CAP_CHK_ALIGN ) ) );
     311           0 :     action->perm( &args, chk, &config );
     312           0 :     ulong err_cnt = fd_cap_chk_err_cnt( chk );
     313           0 :     if( FD_UNLIKELY( err_cnt ) ) {
     314           0 :       for( ulong i=0UL; i<err_cnt; i++ ) FD_LOG_WARNING(( "%s", fd_cap_chk_err( chk, i ) ));
     315             : 
     316           0 :       if( FD_LIKELY( !strcmp( action->name, "run" ) ) ) {
     317           0 :         FD_LOG_ERR(( "insufficient permissions to execute command `%s`. It is recommended "
     318           0 :                      "to start Firedancer as the root user, but you can also start it "
     319           0 :                      "with the missing capabilities listed above. The program only needs "
     320           0 :                      "to start with elevated permissions to do privileged operations at "
     321           0 :                      "boot, and will immediately drop permissions and switch to the user "
     322           0 :                      "specified in your configuration file once they are complete. Firedancer "
     323           0 :                      "will not execute outside of the boot process as root, and will refuse "
     324           0 :                      "to start if it cannot drop privileges. Firedancer needs to be started "
     325           0 :                      "privileged to configure high performance networking with XDP.", action->name ));
     326           0 :       } else if( FD_LIKELY( !strcmp( action->name, "configure" ) ) ) {
     327           0 :         FD_LOG_ERR(( "insufficient permissions to execute command `%s`. It is recommended "
     328           0 :                      "to configure Firedancer as the root user. Firedancer configuration requires "
     329           0 :                      "root because it does privileged operating system actions like mounting huge page filesystems. "
     330           0 :                      "Configuration is a local action that does not access the network, and the process "
     331           0 :                      "exits immediately once configuration completes. The user that Firedancer runs "
     332           0 :                      "as is specified in your configuration file, and although configuration runs as root "
     333           0 :                      "it will permission the relevant resources for the user in your configuration file, "
     334           0 :                      "which can be an anonymous maximally restrictive account with no privileges.", action->name ));
     335           0 :       } else {
     336           0 :         FD_LOG_ERR(( "insufficient permissions to execute command `%s`", action->name ));
     337           0 :       }
     338           0 :     }
     339           0 :   }
     340             : 
     341             :   /* run the command */
     342           0 :   action->fn( &args, &config );
     343             : 
     344           0 :   return 0;
     345           0 : }

Generated by: LCOV version 1.14