LCOV - code coverage report
Current view: top level - app/fdctl - main1.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 213 0.0 %
Date: 2024-11-13 11:58:15 Functions: 0 7 0.0 %

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

Generated by: LCOV version 1.14