LCOV - code coverage report
Current view: top level - discof/backup - fd_snapsv_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1187 0.0 %
Date: 2026-08-16 04:31:21 Functions: 0 46 0.0 %

          Line data    Source code
       1             : /* The snapsv tile is a HTTP file server that serves local snapshots to
       2             :    remote peers.
       3             : 
       4             :    This tile uses io_uring for network and file I/O.
       5             : 
       6             :    snapsv is fully cooperatively scheduled using io_uring waits.
       7             :    It wakes on network events, timeouts, file I/O completions, and
       8             :    futex wakes (new tango messages).
       9             : 
      10             :    Scheduling is event-driven (lazy).  Apart from the initial accept op,
      11             :    every other op is triggered by a completion. */
      12             : 
      13             : #define _GNU_SOURCE
      14             : #include "fd_snapmk_tile.h"
      15             : #include "../../disco/topo/fd_topo.h"
      16             : #include "../../disco/metrics/fd_metrics.h"
      17             : #include "../../util/net/fd_ip6.h"
      18             : #include <errno.h>
      19             : #include <fcntl.h>
      20             : #include <netinet/in.h>
      21             : #include <sys/socket.h>
      22             : #include <linux/futex.h>
      23             : #include <linux/io_uring.h>
      24             : #include "../../util/io_uring/fd_io_uring.h"
      25             : #include "../../util/io_uring/fd_io_uring_setup.h"
      26             : #include "../../util/io_uring/fd_io_uring_register.h"
      27             : #include "../../third_party/picohttpparser/picohttpparser.h"
      28             : #include "generated/fd_snapsv_tile_seccomp.h"
      29             : 
      30             : /* Open addressed hash map of snapshots */
      31             : 
      32             : struct snap_key {
      33             :   ulong slot;
      34             :   ulong base_slot; /* ULONG_MAX if full snap */
      35             : };
      36             : typedef struct snap_key snap_key_t;
      37             : 
      38             : struct snap_entry {
      39             :   snap_key_t key;
      40             :   uchar hash[ 32 ];
      41             :   ulong sz;
      42             :   int   fd;
      43             :   uint  locked:1;
      44             :   uint  is_zstd:1;
      45             : };
      46             : typedef struct snap_entry snap_entry_t;
      47             : 
      48             : static ulong
      49             : snap_key_hash( snap_key_t const * key,
      50           0 :                ulong              seed ) {
      51             :   /* xxHash3 adapted */
      52           0 :   ulong k0 = key->base_slot;
      53           0 :   ulong k1 = key->slot;
      54             : 
      55           0 :   ulong lo = k0 ^ (0x6782737bea4239b9UL + seed);
      56           0 :   ulong hi = k1 ^ (0xaf56bc3b0996523aUL - seed);
      57             : 
      58           0 :   uint128 product = (uint128)lo * (uint128)hi;
      59           0 :   ulong   fold    = (ulong)product ^ (ulong)( product>>64 );
      60             : 
      61           0 :   ulong acc = 16UL + fd_ulong_bswap( lo ) + hi + fold;
      62           0 :   acc ^= acc >> 37;
      63           0 :   acc *= 0x165667919E3779F9UL;
      64           0 :   acc ^= acc >> 32;
      65           0 :   return acc;
      66           0 : }
      67             : 
      68             : #define MAP_NAME            snap_map
      69           0 : #define MAP_ELE_T           snap_entry_t
      70             : #define MAP_KEY_T           snap_key_t
      71           0 : #define MAP_KEY_EQ(k0,k1)   (((k0)->slot==(k1)->slot) & ((k0)->base_slot==(k1)->base_slot))
      72           0 : #define MAP_KEY_HASH(k,s)   snap_key_hash( (k), (s) )
      73           0 : #define MAP_ELE_IS_FREE(e)  (((e)->key.slot==ULONG_MAX) & ((e)->key.base_slot==ULONG_MAX))
      74           0 : #define MAP_ELE_FREE(c,e)   ((e)->key=(snap_key_t){ULONG_MAX,ULONG_MAX})
      75           0 : #define MAP_ELE_MOVE(c,d,s) do { MAP_ELE_T * _src = (s); (*(d)) = *_src; _src->key = (snap_key_t){ULONG_MAX,ULONG_MAX}; } while(0)
      76             : #include "../../util/tmpl/fd_map_slot.c"
      77             : 
      78             : /* Tile state */
      79             : 
      80             : #define IN_LINK_MAX 1 /* cannot trivially grow */
      81             : 
      82             : /* 8 ought to be enough ... The SQE/CQE count per conn is limited due
      83             :    to fixed depth pipelining. */
      84           0 : #define SQE_PER_CONN (8UL)
      85           0 : #define CQE_PER_CONN (8UL)
      86             : 
      87             : /* io_uring fixed file indices */
      88             : 
      89           0 : #define FIXED_FD_LISTEN (0U) /* TCP listen socket */
      90           0 : #define FIXED_FD_CNT    (1U)
      91             : 
      92             : /* HTTP request head limits.  The request head is accumulated in the
      93             :    conn's iobuf, therefore it can never exceed the iobuf size. */
      94             : 
      95           0 : #define REQ_HEADER_MAX (64UL)  /* max number of request headers */
      96             : 
      97             : /* conn state */
      98             : 
      99           0 : #define CONN_STATE_FREE          (0U) /* conn slot is unused */
     100           0 : #define CONN_STATE_REQ_PEEK      (1U) /* peeking request bytes */
     101           0 : #define CONN_STATE_REQ_SKIP      (2U) /* consume in-flight, head still incomplete */
     102           0 : #define CONN_STATE_REQ_DONE      (3U) /* consume in-flight, head complete */
     103           0 : #define CONN_STATE_RES_WRITE_ERR (4U) /* writing response header */
     104           0 : #define CONN_STATE_RES_WRITE_HDR (5U) /* writing response header */
     105           0 : #define CONN_STATE_RES_REDIRECT  (6U) /* writing snap redirect */
     106           0 : #define CONN_STATE_RES_SHOVEL    (7U) /* pipelinined snap data RX & TX */
     107             : 
     108             : struct snapsv_conn {
     109             :   uint state;     /* CONN_STATE_{...} */
     110             :   uint iobuf_idx; /* iobuf for networking, UINT_MAX if none */
     111             :   uint rdbuf_idx; /* iobuf for disk read, UINT_MAX if none */
     112             :   uint rdbuf_len; /* completed bytes in rdbuf */
     113             : 
     114             :   uint sick:1;          /* force close after current request? */
     115             :   uint head:1;          /* HEAD request? */
     116             :   uint range:1;         /* range request? */
     117             :   uint incremental:1;   /* incremental snap requested? */
     118             :   uint disk_inflight:1; /* file read pending completion */
     119             :   uint net_inflight:1;  /* tcp send pending completion */
     120             :   uint closing:1;       /* close once shovel operations drain? */
     121             :   uint http_err;        /* http status code */
     122             : 
     123             :   struct {
     124             :     uint len;
     125             :   } req;
     126             : 
     127             :   struct {
     128             :     uint sent;
     129             :     uint len;
     130             :   } res;
     131             : 
     132             :   struct {
     133             :     snap_key_t key;
     134             :     snap_entry_t * slot;
     135             :     ulong range0; /* next file offset to read */
     136             :     ulong range1; /* exclusive response body end */
     137             :   } snap;
     138             : 
     139             :   long                     request_start_nanos;
     140             :   struct __kernel_timespec idle_timeout;
     141             : 
     142             :   fd_ip6_addr_t peer_ip;
     143             :   ushort        peer_port;
     144             : };
     145             : 
     146             : typedef struct snapsv_conn snapsv_conn_t;
     147             : 
     148             : struct fd_snapsv {
     149             : 
     150             :   /* io_uring */
     151             :   fd_io_uring_t ring[1];
     152             :   void *        ring_shmem;
     153             : 
     154             :   /* I/O buffers */
     155             :   uchar * iobuf0;
     156             :   uint *  iobuf_free;
     157             :   uint    iobuf_free_cnt;
     158             :   uint    iobuf_sz;
     159             : 
     160             :   /* TCP sockets */
     161             :   int             listen_fd;
     162             :   uint            conn0_fd_idx; /* io_uring file index */
     163             :   ulong           conn_cnt;
     164             :   ulong           conn_max;
     165             :   snapsv_conn_t * conn0;
     166             :   uint *          conn_free;
     167             :   uint            conn_free_cnt;
     168             :   long            idle_timeout_nanos;
     169             :   struct __kernel_timespec send_timeout;
     170             : 
     171             :   /* Conn accept */
     172             :   uint                    accept_inflight:1;
     173             :   struct sockaddr_storage accept_addr;
     174             :   socklen_t               accept_addr_len;
     175             : 
     176             :   /* snapshot hashmap */
     177             :   snap_map_t snap_map[1];
     178             :   ulong      snap_cnt_full;
     179             :   ulong      snap_cnt_incr;
     180             :   ulong      snap_max;
     181             : 
     182             :   /* cache: newest snaps */
     183             :   snap_entry_t * newest_full;
     184             :   snap_entry_t * newest_incr;
     185             : 
     186             :   /* snapshot files */
     187             :   int dir_fd;
     188             : 
     189             :   /* tango input links */
     190             :   uchar in_kind[ IN_LINK_MAX ];
     191             :   struct {
     192             :     void * mem;
     193             :     ulong  chunk0;
     194             :     ulong  wmark;
     195             :     ulong  mtu;
     196             : 
     197             :     fd_frag_meta_t const * mcache;
     198             :     ulong const *          seq_prod; /* mcache->seq[0] */
     199             :     ulong                  seq_cons; /* last consumed seq */
     200             :     uint                   depth;
     201             :     uint                   futex_armed:1;
     202             :   } in[ IN_LINK_MAX ];
     203             : 
     204             : };
     205             : 
     206             : typedef struct fd_snapsv fd_snapsv_t;
     207             : 
     208             : /* snapsv_udata_t represents io_uring user data. */
     209             : 
     210             : union snapsv_udata {
     211             :   ulong user_data;
     212             : 
     213             :   struct __attribute__((packed)) {
     214             :     uint  conn_idx;
     215             :     uchar op;
     216           0 : #define UDATA_OP_ACCEPT       ((uchar)0) /* accept new conn */
     217           0 : #define UDATA_OP_PEEK         ((uchar)1) /* read request bytes */
     218           0 : #define UDATA_OP_CONSUME_FRAG ((uchar)2) /* consume request frag */
     219           0 : #define UDATA_OP_CONSUME_TAIL ((uchar)3) /* consume complete request */
     220           0 : #define UDATA_OP_WRITE_HDR    ((uchar)4) /* write response header */
     221           0 : #define UDATA_OP_SHOVEL_DISK  ((uchar)5) /* read snapshot data */
     222           0 : #define UDATA_OP_SHOVEL_NET   ((uchar)6) /* send snapshot data */
     223           0 : #define UDATA_OP_FUTEX        ((uchar)7) /* wait for tango frag */
     224           0 : #define UDATA_OP_TIMEOUT      ((uchar)8) /* linked socket op timeout */
     225             :   };
     226             : };
     227             : 
     228             : typedef union snapsv_udata snapsv_udata_t;
     229             : 
     230             : FD_STATIC_ASSERT( sizeof(snapsv_udata_t)==sizeof(ulong), layout );
     231             : 
     232           0 : #define IN_KIND_SNAPMK 0
     233             : 
     234             : static ulong
     235           0 : scratch_align( void ) {
     236           0 :   return FD_SHMEM_NORMAL_PAGE_SZ;
     237           0 : }
     238             : 
     239             : FD_FN_PURE static ulong
     240           0 : snapsv_fd_max( fd_topo_tile_t const * tile ) {
     241           0 :   return FIXED_FD_CNT + tile->snapsv.snap_max + tile->snapsv.conn_max;
     242           0 : }
     243             : 
     244             : FD_FN_PURE static ulong
     245           0 : snapsv_map_ele_max( fd_topo_tile_t const * tile ) {
     246           0 :   return fd_ulong_pow2_up( tile->snapsv.snap_max );
     247           0 : }
     248             : 
     249             : FD_FN_PURE static ulong
     250           0 : snapsv_sq_depth( fd_topo_tile_t const * tile ) {
     251           0 :   return fd_ulong_pow2_up( SQE_PER_CONN*tile->snapsv.conn_max );
     252           0 : }
     253             : 
     254             : FD_FN_PURE static ulong
     255           0 : snapsv_cq_depth( fd_topo_tile_t const * tile ) {
     256           0 :   return fd_ulong_pow2_up( CQE_PER_CONN*tile->snapsv.conn_max );
     257           0 : }
     258             : 
     259             : static ulong
     260           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
     261           0 :   ulong sq_depth = snapsv_sq_depth( tile );
     262           0 :   ulong cq_depth = snapsv_cq_depth( tile );
     263           0 :   ulong l = FD_LAYOUT_INIT;
     264           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     265           0 :   l = FD_LAYOUT_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     266           0 :   l = FD_LAYOUT_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     267           0 :   l = FD_LAYOUT_APPEND( l, alignof(int),              snapsv_fd_max( tile )*sizeof(int) );
     268           0 :   l = FD_LAYOUT_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     269           0 :   l = FD_LAYOUT_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     270           0 :   l = FD_LAYOUT_APPEND( l, alignof(uint),             tile->snapsv.conn_max * 2 * sizeof(uint) );
     271           0 :   l = FD_LAYOUT_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max * (tile->snapsv.send_buffer_size_kib<<11) );
     272           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
     273           0 : }
     274             : 
     275             : /* snapsv_listen creates the TCP listen socket */
     276             : 
     277             : static int
     278             : snapsv_listen( fd_ip6_addr_t const * listen_addr,
     279             :                ushort                listen_port,
     280           0 :                ulong                 conn_max ) {
     281           0 :   int ip4 = fd_ip6_addr_is_ip4_mapped( listen_addr->addr ) && !listen_addr->scope_id;
     282             : 
     283           0 :   int sock_fd = socket( ip4 ? AF_INET : AF_INET6, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     284           0 :   if( FD_UNLIKELY( -1==sock_fd ) ) {
     285           0 :     FD_LOG_ERR(( "socket(AF_INET%s,SOCK_STREAM) failed (%i-%s)", ip4 ? "" : "6", errno, fd_io_strerror( errno ) ));
     286           0 :   }
     287             : 
     288             :   /* recover quickly from restart, if there are lingering TIME_WAIT conns */
     289           0 :   int optval = 1;
     290           0 :   if( FD_UNLIKELY( -1==setsockopt( sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int) ) ) ) {
     291           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_REUSEADDR) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     292           0 :   }
     293             :   /* load balancing across multiple snapsv tiles */
     294           0 :   optval = 1;
     295           0 :   if( FD_UNLIKELY( -1==setsockopt( sock_fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(int) ) ) ) {
     296           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_REUSEPORT) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     297           0 :   }
     298             : 
     299           0 :   union {
     300           0 :     struct sockaddr_in  ip4;
     301           0 :     struct sockaddr_in6 ip6;
     302           0 :   } addr = {0};
     303           0 :   ulong addr_sz;
     304             : 
     305           0 :   if( ip4 ) {
     306           0 :     addr.ip4 = (struct sockaddr_in){
     307           0 :       .sin_family      = AF_INET,
     308           0 :       .sin_port        = fd_ushort_bswap( listen_port ),
     309           0 :       .sin_addr.s_addr = fd_ip6_addr_to_ip4( listen_addr->addr )
     310           0 :     };
     311           0 :     addr_sz = sizeof(struct sockaddr_in);
     312           0 :   } else {
     313           0 :     int v6only = 0;
     314           0 :     if( FD_UNLIKELY( -1==setsockopt( sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(int) ) ) ) {
     315           0 :       FD_LOG_ERR(( "setsockopt(IPPROTO_IPV6,IPV6_V6ONLY) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     316           0 :     }
     317           0 :     addr.ip6 = (struct sockaddr_in6){
     318           0 :       .sin6_family   = AF_INET6,
     319           0 :       .sin6_port     = fd_ushort_bswap( listen_port ),
     320           0 :       .sin6_scope_id = listen_addr->scope_id
     321           0 :     };
     322           0 :     memcpy( addr.ip6.sin6_addr.s6_addr, listen_addr->addr, 16UL );
     323           0 :     addr_sz = sizeof(struct sockaddr_in6);
     324           0 :   }
     325             : 
     326           0 :   char addr_cstr[ FD_IP6_ADDR_CSTR_MAX ];
     327           0 :   if( FD_UNLIKELY( -1==bind( sock_fd, fd_type_pun( &addr ), (uint)addr_sz ) ) ) {
     328           0 :     FD_LOG_ERR(( "bind(%s:%u) failed (%i-%s)",
     329           0 :                  fd_ip6_addr_cstr( addr_cstr, listen_addr ), listen_port,
     330           0 :                  errno, fd_io_strerror( errno ) ));
     331           0 :   }
     332           0 :   if( FD_UNLIKELY( -1==listen( sock_fd, (int)conn_max ) ) ) {
     333           0 :     FD_LOG_ERR(( "listen() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     334           0 :   }
     335             : 
     336           0 :   FD_LOG_NOTICE(( "snapshot server listening at %shttp://%s:%u%s",
     337           0 :                   fd_log_style_bold(), fd_ip6_addr_cstr( addr_cstr, listen_addr ), listen_port,
     338           0 :                   fd_log_style_normal() ));
     339             : 
     340           0 :   return sock_fd;
     341           0 : }
     342             : 
     343             : static void
     344             : privileged_init( fd_topo_t const *      topo,
     345           0 :                  fd_topo_tile_t const * tile ) {
     346           0 :   ulong fd_max   = snapsv_fd_max( tile );
     347           0 :   ulong sq_depth = snapsv_sq_depth( tile );
     348           0 :   ulong cq_depth = snapsv_cq_depth( tile );
     349           0 :   FD_SCRATCH_ALLOC_INIT( l, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
     350           0 :   fd_snapsv_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     351           0 :   void *        ring_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     352           0 :   void *        map_mem  = FD_SCRATCH_ALLOC_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     353           0 :   int *         fd_table = FD_SCRATCH_ALLOC_APPEND( l, alignof(int),              fd_max*sizeof(int) );
     354           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     355           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     356           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*2UL*sizeof(uint) );
     357           0 :   uchar *       iobuf0   = FD_SCRATCH_ALLOC_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max*(tile->snapsv.send_buffer_size_kib<<11) );
     358             : 
     359           0 :   memset( ctx, 0, sizeof(fd_snapsv_t) );
     360           0 :   memset( fd_table, 0xff, fd_max*sizeof(int) );
     361             : 
     362             :   /* TCP listen socket */
     363             : 
     364           0 :   ctx->conn_max  = tile->snapsv.conn_max;
     365           0 :   ctx->listen_fd = snapsv_listen( &tile->snapsv.listen_addr, tile->snapsv.listen_port, tile->snapsv.conn_max );
     366             : 
     367             :   /* io_uring setup */
     368             : 
     369           0 :   ctx->ring_shmem = ring_mem;
     370           0 :   fd_io_uring_params_t params[1];
     371           0 :   fd_io_uring_params_init( params, (uint)sq_depth );
     372           0 :   params->flags |= IORING_SETUP_COOP_TASKRUN | IORING_SETUP_DEFER_TASKRUN;
     373             : 
     374           0 :   if( FD_UNLIKELY( !fd_io_uring_init_shmem( ctx->ring, params, ctx->ring_shmem, sq_depth, cq_depth ) ) ) {
     375           0 :     FD_LOG_ERR(( "fd_io_uring_init_shmem failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     376           0 :   }
     377             : 
     378             :   /* registered buffer to help with DMA */
     379             : 
     380           0 :   struct iovec iovec = {
     381           0 :     .iov_base = iobuf0,
     382           0 :     .iov_len  = tile->snapsv.conn_max*(tile->snapsv.send_buffer_size_kib<<11)
     383           0 :   };
     384           0 :   if( FD_UNLIKELY( fd_io_uring_register_buffers( ctx->ring->ioring_fd, &iovec, 1UL )<0 ) ) {
     385           0 :     FD_LOG_ERR(( "io_uring_register_buffers() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     386           0 :   }
     387             : 
     388             :   /* Build file descriptor table */
     389           0 :   FD_TEST( tile->snapsv.snap_max<=FD_SNAP_MAX );
     390           0 :   fd_table[ FIXED_FD_LISTEN ] = ctx->listen_fd;
     391           0 :   uint fixed_fd = FIXED_FD_CNT;
     392           0 :   for( ulong i=0UL; i<tile->snapsv.snap_max; i++ ) {
     393           0 :     fd_table[ fixed_fd++ ] = FD_SNAP_RO_FD( i );
     394           0 :   }
     395           0 :   ctx->conn0_fd_idx = fixed_fd;
     396           0 :   if( FD_UNLIKELY( fd_io_uring_register_files( ctx->ring->ioring_fd, fd_table, fd_max )<0 ) ) {
     397           0 :     FD_LOG_ERR(( "io_uring_register_files() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     398           0 :   }
     399             : 
     400           0 :   uint max_workers[2] = { tile->snapsv.io_worker_cnt, tile->snapsv.io_worker_cnt };
     401           0 :   if( FD_UNLIKELY( fd_io_uring_register( ctx->ring->ioring_fd, IORING_REGISTER_IOWQ_MAX_WORKERS, max_workers, 2U )<0 ) ) {
     402           0 :     FD_LOG_ERR(( "io_uring_register(IORING_REGISTER_IOWQ_MAX_WORKERS) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     403           0 :   }
     404             : 
     405           0 :   fd_io_uring_restriction_t restrictions[] = {
     406           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_ACCEPT       },
     407           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_RECV         },
     408           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_SEND         },
     409           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_READ_FIXED   },
     410           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_FUTEX_WAIT   },
     411           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_LINK_TIMEOUT },
     412           0 :     { .opcode      = FD_IORING_RESTRICTION_REGISTER_OP, /* deregister files */
     413           0 :       .register_op = FD_IORING_REGISTER_FILES_UPDATE },
     414           0 :     { .opcode    = FD_IORING_RESTRICTION_SQE_FLAGS_ALLOWED,
     415           0 :       .sqe_flags = IOSQE_IO_LINK },
     416           0 :     { .opcode    = FD_IORING_RESTRICTION_SQE_FLAGS_REQUIRED,
     417           0 :       .sqe_flags = IOSQE_FIXED_FILE },
     418           0 :   };
     419           0 :   if( FD_UNLIKELY( fd_io_uring_register_restrictions(
     420           0 :         ctx->ring->ioring_fd, restrictions,
     421           0 :         (uint)( sizeof(restrictions)/sizeof(restrictions[0]) ) )<0 ) ) {
     422           0 :     FD_LOG_ERR(( "io_uring_register_restrictions failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     423           0 :   }
     424             : 
     425           0 :   if( FD_UNLIKELY( fd_io_uring_enable_rings( ctx->ring->ioring_fd )<0 ) ) {
     426           0 :     FD_LOG_ERR(( "io_uring_enable_rings failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     427           0 :   }
     428             : 
     429             :   /* snapshot map setup */
     430             : 
     431           0 :   ulong map_ele_max = snapsv_map_ele_max( tile );
     432           0 :   snap_entry_t * slots = snap_map_new( map_mem, map_ele_max, 0 );
     433           0 :   FD_TEST( slots );
     434           0 :   for( ulong i=0UL; i<map_ele_max; i++ ) {
     435           0 :     slots[ i ].key = (snap_key_t){ ULONG_MAX,ULONG_MAX };
     436           0 :   }
     437           0 :   ulong map_seed; FD_TEST( fd_rng_secure( &map_seed, sizeof(map_seed) ) );
     438           0 :   FD_TEST( snap_map_join( ctx->snap_map, slots, map_ele_max, map_ele_max, map_seed ) );
     439           0 :   ctx->snap_max = tile->snapsv.snap_max;
     440           0 : }
     441             : 
     442             : static void
     443             : prep_accept( fd_snapsv_t * ctx );
     444             : 
     445             : static void
     446             : unprivileged_init( fd_topo_t const *      topo,
     447           0 :                    fd_topo_tile_t const * tile ) {
     448           0 :   ulong sq_depth = snapsv_sq_depth( tile );
     449           0 :   ulong cq_depth = snapsv_cq_depth( tile );
     450           0 :   FD_SCRATCH_ALLOC_INIT( l, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
     451           0 :   fd_snapsv_t * ctx          = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     452           0 :   /*                   */(void)FD_SCRATCH_ALLOC_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     453           0 :   /*                   */(void)FD_SCRATCH_ALLOC_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     454           0 :   /*                   */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(int),              snapsv_fd_max( tile )*sizeof(int) );
     455           0 :   snapsv_conn_t * conn0      = FD_SCRATCH_ALLOC_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     456           0 :   uint *          conn_free  = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     457           0 :   uint *          iobuf_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max * 2 * sizeof(uint) );
     458           0 :   uchar *         iobuf0     = FD_SCRATCH_ALLOC_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max * (tile->snapsv.send_buffer_size_kib<<11) );
     459             : 
     460           0 :   FD_CHECK_ERR( tile->in_cnt<=IN_LINK_MAX, "too many input links" );
     461           0 :   FD_CHECK_ERR( tile->in_cnt==1, "snapsv is hardcoded to only support one input link" );
     462           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     463           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
     464           0 :     FD_CHECK_ERR( !strcmp( link->name, "snapmk_out" ), "unexpected input link" );
     465           0 :     FD_CHECK_ERR( tile->in_link_poll[ i ], "expecting polled input link" );
     466           0 :     ctx->in_kind[ i ]   = IN_KIND_SNAPMK;
     467           0 :     ctx->in[ i ].mem    = fd_wksp_containing( link->dcache );
     468           0 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
     469           0 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark( ctx->in[ i ].mem, link->dcache, link->mtu );
     470           0 :     ctx->in[ i ].mtu    = link->mtu;
     471             : 
     472           0 :     ulong depth = fd_mcache_depth( link->mcache );
     473           0 :     FD_CHECK_ERR( depth<=UINT_MAX, "input link mcache too deep" );
     474           0 :     ctx->in[ i ].mcache      = link->mcache;
     475           0 :     ctx->in[ i ].depth       = (uint)depth;
     476           0 :     ctx->in[ i ].seq_prod    = fd_mcache_seq_laddr_const( link->mcache );
     477           0 :     ctx->in[ i ].seq_cons    = ULONG_MAX;
     478           0 :     ctx->in[ i ].futex_armed = 0;
     479           0 :   }
     480             : 
     481           0 :   ctx->conn0         = conn0;
     482           0 :   ctx->conn_free     = conn_free;
     483           0 :   ctx->conn_free_cnt = (uint)tile->snapsv.conn_max;
     484           0 :   ctx->idle_timeout_nanos = (long)( tile->snapsv.idle_timeout_millis * ((ulong)1e6) );
     485           0 :   long send_timeout_nanos = (long)( tile->snapsv.send_timeout_millis * ((ulong)1e6) );
     486           0 :   ctx->send_timeout = (struct __kernel_timespec) {
     487           0 :     .tv_sec  = send_timeout_nanos / (long)1e9,
     488           0 :     .tv_nsec = send_timeout_nanos % (long)1e9
     489           0 :   };
     490           0 :   for( ulong i=0UL; i<tile->snapsv.conn_max; i++ ) {
     491           0 :     conn0[ i ]     = (snapsv_conn_t){ .state = CONN_STATE_FREE, .iobuf_idx = UINT_MAX, .rdbuf_idx = UINT_MAX };
     492           0 :     conn_free[ i ] = (uint)tile->snapsv.conn_max-1U-(uint)i;
     493           0 :   }
     494             : 
     495           0 :   FD_CHECK_ERR( tile->snapsv.send_buffer_size_kib, "send_buffer_size_kib is zero" );
     496           0 :   ulong iobuf_cnt = tile->snapsv.conn_max * 2;
     497           0 :   ctx->iobuf_sz       = (uint)( tile->snapsv.send_buffer_size_kib<<10 );
     498           0 :   ctx->iobuf0         = iobuf0;
     499           0 :   ctx->iobuf_free     = iobuf_free;
     500           0 :   ctx->iobuf_free_cnt = (uint)iobuf_cnt;
     501           0 :   for( ulong i=0UL; i<iobuf_cnt; i++ ) {
     502           0 :     iobuf_free[ i ] = (uint)iobuf_cnt-1U-(uint)i;
     503           0 :   }
     504             : 
     505           0 :   prep_accept( ctx );
     506           0 : }
     507             : 
     508             : /* prep_accept enqueues an io_uring conn accept SQE. */
     509             : 
     510             : static void
     511           0 : prep_accept( fd_snapsv_t * ctx ) {
     512           0 :   FD_CHECK_ERR( !ctx->accept_inflight, "accept already inflight" );
     513           0 :   FD_CHECK_ERR( ctx->conn_cnt < ctx->conn_max, "conn table full" );
     514           0 :   FD_CHECK_ERR( ctx->conn_free_cnt, "conn free list empty" );
     515             : 
     516           0 :   uint            conn_idx = ctx->conn_free[ --ctx->conn_free_cnt ];
     517           0 :   snapsv_conn_t * conn     = &ctx->conn0[ conn_idx ];
     518           0 :   *conn = (snapsv_conn_t){ .state = CONN_STATE_FREE, .iobuf_idx = UINT_MAX, .rdbuf_idx = UINT_MAX };
     519           0 :   uint fd_idx = ctx->conn0_fd_idx + conn_idx;
     520           0 :   ctx->accept_addr_len = sizeof(ctx->accept_addr);
     521             : 
     522           0 :   fd_io_uring_t * ring = ctx->ring;
     523           0 :   struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ring->sq );
     524           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     525           0 :   *sqe = (struct io_uring_sqe) {
     526           0 :     .opcode     = FD_IORING_OP_ACCEPT,
     527           0 :     .flags      = IOSQE_FIXED_FILE,
     528           0 :     .fd         = FIXED_FD_LISTEN,
     529           0 :     .off        = (ulong)&ctx->accept_addr_len,
     530           0 :     .addr       = (ulong)&ctx->accept_addr,
     531           0 :     .len        = 0,
     532           0 :     .file_index = fd_idx + 1U /* registered files are 1-indexed */
     533           0 :   };
     534           0 :   snapsv_udata_t udata = { .op = UDATA_OP_ACCEPT, .conn_idx = conn_idx };
     535           0 :   sqe->user_data = udata.user_data;
     536           0 :   ctx->accept_inflight = 1;
     537           0 : }
     538             : 
     539             : /* conn_iobuf returns a pointer to the current I/O buffer of a conn. */
     540             : 
     541             : FD_FN_PURE static uchar *
     542             : conn_iobuf( fd_snapsv_t const *   ctx,
     543           0 :             snapsv_conn_t const * conn ) {
     544           0 :   FD_CHECK_CRIT( conn->iobuf_idx!=UINT_MAX, "conn has no iobuf" );
     545           0 :   return ctx->iobuf0 + (ulong)conn->iobuf_idx*ctx->iobuf_sz;
     546           0 : }
     547             : 
     548             : /* iobuf_acquire lends an iobuf to a conn. */
     549             : 
     550             : static void
     551             : iobuf_acquire( fd_snapsv_t * ctx,
     552           0 :                uint *        slot ) {
     553           0 :   if( FD_LIKELY( *slot!=UINT_MAX ) ) return;
     554             :   /* Every conn holds at most one buffer and the pool has two per conn,
     555             :      so a conn that needs one always finds one. */
     556           0 :   FD_CHECK_CRIT( ctx->iobuf_free_cnt, "iobuf pool exhausted" );
     557           0 :   *slot = ctx->iobuf_free[ --ctx->iobuf_free_cnt ];
     558           0 : }
     559             : 
     560             : /* iobuf_release frees a conn iobuf (if any). */
     561             : 
     562             : static void
     563             : iobuf_release( fd_snapsv_t * ctx,
     564           0 :                uint *        slot ) {
     565           0 :   if( FD_UNLIKELY( *slot==UINT_MAX ) ) return;
     566           0 :   ctx->iobuf_free[ ctx->iobuf_free_cnt++ ] = *slot;
     567           0 :   *slot = UINT_MAX;
     568           0 : }
     569             : 
     570             : /* prep_peek enqueues a receive op to copy any newly arrived bytes into
     571             :    iobuf.  Does not advance the socket's file pointer. */
     572             : 
     573             : static void
     574             : prep_peek( fd_snapsv_t * ctx,
     575           0 :            uint          conn_idx ) {
     576           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     577           0 :   long now = fd_log_wallclock();
     578           0 :   if( FD_UNLIKELY( !conn->request_start_nanos ) ) {
     579           0 :     conn->request_start_nanos = now;
     580           0 :   }
     581           0 :   long idle_rem = fd_long_max( 1L, ctx->idle_timeout_nanos - (now - conn->request_start_nanos) );
     582           0 :   conn->idle_timeout = (struct __kernel_timespec) {
     583           0 :     .tv_sec  = idle_rem / (long)1e9,
     584           0 :     .tv_nsec = idle_rem % (long)1e9
     585           0 :   };
     586           0 :   uint            len  = ctx->iobuf_sz - conn->req.len;
     587           0 :   FD_CHECK_CRIT( len, "request head buffer is full" );
     588           0 :   iobuf_acquire( ctx, &conn->iobuf_idx );
     589             : 
     590           0 :   fd_io_uring_t * ring = ctx->ring;
     591           0 :   struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ring->sq );
     592           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     593           0 :   *sqe = (struct io_uring_sqe) {
     594           0 :     .opcode    = FD_IORING_OP_RECV,
     595           0 :     .flags     = IOSQE_FIXED_FILE | IOSQE_IO_LINK,
     596             :     //.ioprio    = IORING_RECVSEND_FIXED_BUF,
     597           0 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     598           0 :     .addr      = (ulong)( conn_iobuf( ctx, conn ) + conn->req.len ),
     599           0 :     .len       = len,
     600             :     //.buf_index = 0U,
     601           0 :     .msg_flags = MSG_PEEK
     602           0 :   };
     603           0 :   snapsv_udata_t udata = { .op = UDATA_OP_PEEK, .conn_idx = conn_idx };
     604           0 :   sqe->user_data = udata.user_data;
     605             : 
     606           0 :   sqe = fd_io_uring_get_sqe( ring->sq );
     607           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     608           0 :   *sqe = (struct io_uring_sqe) {
     609           0 :     .opcode = FD_IORING_OP_LINK_TIMEOUT,
     610           0 :     .flags  = IOSQE_FIXED_FILE, /* ignored */
     611           0 :     .addr   = (ulong)&conn->idle_timeout,
     612           0 :     .len    = 1U
     613           0 :   };
     614           0 :   udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
     615           0 :   sqe->user_data = udata.user_data;
     616           0 : }
     617             : 
     618             : /* prep_consume consumes previously received (via peek) iobuf bytes. */
     619             : 
     620             : static void
     621             : prep_consume( fd_snapsv_t * ctx,
     622             :               uint          conn_idx,
     623             :               uint          len,
     624           0 :               uchar         op ) {
     625           0 :   FD_CHECK_CRIT( len, "consuming zero bytes" );
     626           0 :   fd_io_uring_t * ring = ctx->ring;
     627           0 :   struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ring->sq );
     628           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     629           0 :   *sqe = (struct io_uring_sqe) {
     630           0 :     .opcode    = FD_IORING_OP_RECV,
     631           0 :     .flags     = IOSQE_FIXED_FILE,
     632           0 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     633           0 :     .addr      = 0UL,
     634           0 :     .len       = len,
     635           0 :     .msg_flags = MSG_TRUNC | MSG_WAITALL
     636           0 :   };
     637           0 :   snapsv_udata_t udata = { .op = op, .conn_idx = conn_idx };
     638           0 :   sqe->user_data = udata.user_data;
     639           0 : }
     640             : 
     641             : /* conn_close destroys a conn object.  If no accept op is in-flight,
     642             :    enqueues one. */
     643             : 
     644             : static void
     645             : conn_close( fd_snapsv_t * ctx,
     646           0 :             uint          conn_idx ) {
     647           0 :   FD_CHECK_CRIT( conn_idx < ctx->conn_max, "invalid conn_idx" );
     648           0 :   snapsv_conn_t * conn   = &ctx->conn0[ conn_idx ];
     649           0 :   uint            fd_idx = ctx->conn0_fd_idx + conn_idx;
     650             : 
     651             :   /* io_uring OP_CLOSE on a fixed file does not use IOSQE_FIXED_FILE,
     652             :      therefore would break the io_uring sandbox (restrictions). */
     653           0 :   int unreg = -1;
     654           0 :   if( FD_UNLIKELY( fd_io_uring_register_files_update( ctx->ring->ioring_fd, fd_idx, &unreg, 1U )<0 ) ) {
     655           0 :     FD_LOG_ERR(( "io_uring_register(IORING_REGISTER_FILES_UPDATE) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     656           0 :   }
     657             : 
     658           0 :   iobuf_release( ctx, &conn->iobuf_idx );
     659           0 :   iobuf_release( ctx, &conn->rdbuf_idx );
     660           0 :   *conn = (snapsv_conn_t){ .state = CONN_STATE_FREE, .iobuf_idx = UINT_MAX, .rdbuf_idx = UINT_MAX };
     661             : 
     662           0 :   FD_CHECK_CRIT( ctx->conn_cnt, "conn count underflow" );
     663           0 :   ctx->conn_cnt--;
     664           0 :   ctx->conn_free[ ctx->conn_free_cnt++ ] = conn_idx;
     665             : 
     666           0 :   if( FD_UNLIKELY( !ctx->accept_inflight ) ) {
     667           0 :     prep_accept( ctx );
     668           0 :   }
     669           0 : }
     670             : 
     671             : static ulong
     672             : populate_allowed_fds( fd_topo_t const *      topo,
     673             :                       fd_topo_tile_t const * tile,
     674             :                       ulong                  out_fds_cnt,
     675           0 :                       int *                  out_fds ) {
     676           0 :   fd_snapsv_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     677           0 :   if( FD_UNLIKELY( out_fds_cnt<4+ctx->snap_max ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
     678           0 :   ulong out_cnt = 0UL;
     679           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
     680           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
     681           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
     682           0 :   out_fds[ out_cnt++ ] = ctx->ring->ioring_fd;
     683           0 :   out_fds[ out_cnt++ ] = ctx->listen_fd;
     684           0 :   for( ulong i=0UL; i<ctx->snap_max; i++ )
     685           0 :     out_fds[ out_cnt++ ] = FD_SNAP_RO_FD( i ); /* snapshot pool */
     686           0 :   return out_cnt;
     687           0 : }
     688             : 
     689             : static ulong
     690             : populate_allowed_seccomp( fd_topo_t const *      topo,
     691             :                           fd_topo_tile_t const * tile,
     692             :                           ulong                  out_cnt,
     693           0 :                           struct sock_filter *   out ) {
     694           0 :   fd_snapsv_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     695           0 :   populate_sock_filter_policy_fd_snapsv_tile(
     696           0 :       out_cnt, out,
     697           0 :       (uint)fd_log_private_logfile_fd(),
     698           0 :       (uint)ctx->ring->ioring_fd,
     699           0 :       (uint)FD_SNAP_RO_FD( 0 ),
     700           0 :       (uint)FD_SNAP_RO_FD( ctx->snap_max-1UL ) );
     701           0 :   return sock_filter_policy_fd_snapsv_tile_instr_cnt;
     702           0 : }
     703             : 
     704             : static ulong
     705             : rlimit_file_cnt( fd_topo_t const *      topo,
     706           0 :                  fd_topo_tile_t const * tile ) {
     707           0 :   (void)topo;
     708             :   /* stderr, logfile, boot control pipe, io_uring, listen socket, one
     709             :      spare for accept, the snapshot pool, and active connections */
     710           0 :   return 6UL + tile->snapsv.snap_max + tile->snapsv.conn_max;
     711           0 : }
     712             : 
     713             : /* handle_accept handles the completion of an accept op. */
     714             : 
     715             : static void
     716             : handle_accept( fd_snapsv_t * ctx,
     717             :                uint          conn_idx,
     718           0 :                int           res ) {
     719           0 :   ctx->accept_inflight = 0;
     720             : 
     721           0 :   if( FD_UNLIKELY( res<0 ) ) {
     722           0 :     FD_LOG_WARNING(( "accept() failed (%i-%s)", -res, fd_io_strerror( -res ) ));
     723           0 :     ctx->conn_free[ ctx->conn_free_cnt++ ] = conn_idx;
     724           0 :     prep_accept( ctx );
     725           0 :     return;
     726           0 :   }
     727             : 
     728           0 :   ctx->conn_cnt++;
     729             : 
     730             :   /* set conn addr */
     731           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     732           0 :   switch( ctx->accept_addr.ss_family ) {
     733           0 :   case AF_INET: {
     734           0 :     struct sockaddr_in const * addr = fd_type_pun_const( &ctx->accept_addr );
     735           0 :     fd_ip6_addr_ip4_mapped( conn->peer_ip.addr, addr->sin_addr.s_addr );
     736           0 :     conn->peer_ip.scope_id = 0U;
     737           0 :     conn->peer_port = fd_ushort_bswap( addr->sin_port );
     738           0 :     break;
     739           0 :   }
     740           0 :   case AF_INET6: {
     741           0 :     struct sockaddr_in6 const * addr = fd_type_pun_const( &ctx->accept_addr );
     742           0 :     memcpy( conn->peer_ip.addr, addr->sin6_addr.s6_addr, 16UL );
     743           0 :     conn->peer_ip.scope_id = addr->sin6_scope_id;
     744           0 :     conn->peer_port = fd_ushort_bswap( addr->sin6_port );
     745           0 :     break;
     746           0 :   }
     747           0 :   default:
     748           0 :     FD_LOG_WARNING(( "accept() returned unknown address family %i", (int)ctx->accept_addr.ss_family ));
     749           0 :     memset( &conn->peer_ip, 0, sizeof(conn->peer_ip) );
     750           0 :     conn->peer_port = 0U;
     751           0 :   }
     752             : 
     753           0 :   prep_peek( ctx, conn_idx );
     754           0 :   if( FD_LIKELY( ctx->conn_cnt<ctx->conn_max ) ) {
     755           0 :     prep_accept( ctx );
     756           0 :   }
     757           0 : }
     758             : 
     759             : /* conn_snap_entry checks that the snapshot for the current operation is
     760             :    still valid.  The return value of this function is invalidated once
     761             :    after_credit returns. */
     762             : 
     763             : static snap_entry_t *
     764           0 : conn_snap_entry( snapsv_conn_t * conn ) {
     765           0 :   snap_entry_t * slot = conn->snap.slot;
     766           0 :   FD_CHECK_ERR( slot, "snapshot slot is NULL" );
     767           0 :   if( FD_UNLIKELY( ( slot->key.base_slot != conn->snap.key.base_slot ) |
     768           0 :                    ( slot->key.slot      != conn->snap.key.slot      ) ) ) {
     769             :     /* snapshot was deleted */
     770           0 :     return NULL;
     771           0 :   }
     772           0 :   return slot;
     773           0 : }
     774             : 
     775             : static int
     776             : snap_lock( snap_entry_t * entry );
     777             : 
     778             : /* shovel dispatches snapshot streaming I/O work.
     779             :    This forms a 2-job deep pipeline that attempts to concurrently do
     780             :    disk read and network write I/O. */
     781             : 
     782             : static void
     783             : shovel( fd_snapsv_t * ctx,
     784           0 :         uint          conn_idx ) {
     785           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     786           0 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_SHOVEL, "conn state confusion" );
     787             : 
     788           0 :   if( FD_UNLIKELY( conn->closing ) ) {
     789           0 :     if( !conn->disk_inflight && !conn->net_inflight ) {
     790           0 :       conn_close( ctx, conn_idx );
     791           0 :     }
     792           0 :     return;
     793           0 :   }
     794             : 
     795           0 :   if( conn->iobuf_idx!=UINT_MAX &&
     796           0 :       conn->res.sent==conn->res.len &&
     797           0 :       !conn->net_inflight ) {
     798           0 :     iobuf_release( ctx, &conn->iobuf_idx );
     799           0 :     conn->res.sent = 0U;
     800           0 :     conn->res.len  = 0U;
     801           0 :   }
     802             : 
     803             :   /* disk read buffer complete, rename to net send buffer */
     804           0 :   if( conn->iobuf_idx==UINT_MAX && conn->rdbuf_len ) {
     805           0 :     FD_CHECK_CRIT( conn->rdbuf_idx!=UINT_MAX, "completed read has no buffer" );
     806           0 :     conn->iobuf_idx = conn->rdbuf_idx;
     807           0 :     conn->rdbuf_idx = UINT_MAX;
     808           0 :     conn->res.sent  = 0U;
     809           0 :     conn->res.len   = conn->rdbuf_len;
     810           0 :     conn->rdbuf_len = 0U;
     811           0 :   }
     812             : 
     813             :   /* generate net send SQE */
     814           0 :   if( conn->iobuf_idx!=UINT_MAX && conn->res.sent<conn->res.len && !conn->net_inflight ) {
     815           0 :     struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ctx->ring->sq );
     816           0 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     817           0 :     *sqe = (struct io_uring_sqe) {
     818           0 :       .opcode    = FD_IORING_OP_SEND,
     819           0 :       .flags     = IOSQE_FIXED_FILE | IOSQE_IO_LINK,
     820             :       //.ioprio    = IORING_RECVSEND_FIXED_BUF,
     821           0 :       .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     822           0 :       .addr      = (ulong)conn_iobuf( ctx, conn ) + conn->res.sent,
     823           0 :       .len       = conn->res.len - conn->res.sent,
     824             :       //.buf_index = 0U,
     825           0 :       .msg_flags = MSG_NOSIGNAL
     826           0 :     };
     827           0 :     snapsv_udata_t udata = { .op = UDATA_OP_SHOVEL_NET, .conn_idx = conn_idx };
     828           0 :     sqe->user_data = udata.user_data;
     829           0 :     conn->net_inflight = 1U;
     830             : 
     831           0 :     sqe = fd_io_uring_get_sqe( ctx->ring->sq );
     832           0 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     833           0 :     *sqe = (struct io_uring_sqe) {
     834           0 :       .opcode = FD_IORING_OP_LINK_TIMEOUT,
     835           0 :       .flags  = IOSQE_FIXED_FILE, /* ignored */
     836           0 :       .addr   = (ulong)&ctx->send_timeout,
     837           0 :       .len    = 1U
     838           0 :     };
     839           0 :     udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
     840           0 :     sqe->user_data = udata.user_data;
     841           0 :   }
     842             : 
     843             :   /* generate disk read SQE */
     844           0 :   if( conn->rdbuf_idx==UINT_MAX && !conn->disk_inflight && conn->snap.range0<conn->snap.range1 ) {
     845           0 :     snap_entry_t const * snap = conn_snap_entry( conn );
     846           0 :     if( FD_UNLIKELY( !snap ) ) {
     847           0 :       conn->closing = 1U;
     848           0 :       shovel( ctx, conn_idx );
     849           0 :       return;
     850           0 :     }
     851             : 
     852           0 :     iobuf_acquire( ctx, &conn->rdbuf_idx );
     853           0 :     ulong read_sz = fd_ulong_min( ctx->iobuf_sz, conn->snap.range1-conn->snap.range0 );
     854           0 :     ulong pool_idx = (ulong)( snap->fd - FD_SNAP_RO_FD( 0 ) );
     855           0 :     FD_CHECK_CRIT( pool_idx<ctx->snap_max, "snapshot has invalid file descriptor" );
     856             : 
     857           0 :     struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ctx->ring->sq );
     858           0 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     859           0 :     *sqe = (struct io_uring_sqe) {
     860           0 :       .opcode    = FD_IORING_OP_READ_FIXED,
     861           0 :       .flags     = IOSQE_FIXED_FILE,
     862           0 :       .fd        = (int)( FIXED_FD_CNT + pool_idx ),
     863           0 :       .off       = conn->snap.range0,
     864           0 :       .addr      = (ulong)( ctx->iobuf0 + (ulong)conn->rdbuf_idx*ctx->iobuf_sz ),
     865           0 :       .len       = (uint)read_sz,
     866           0 :       .buf_index = 0U
     867           0 :     };
     868           0 :     snapsv_udata_t udata = { .op = UDATA_OP_SHOVEL_DISK, .conn_idx = conn_idx };
     869           0 :     sqe->user_data = udata.user_data;
     870           0 :     conn->disk_inflight = 1U;
     871           0 :   }
     872             : 
     873             :   /* everything done */
     874           0 :   if( FD_UNLIKELY( conn->snap.range0>=conn->snap.range1 &&
     875           0 :                    conn->iobuf_idx==UINT_MAX &&
     876           0 :                    conn->rdbuf_idx==UINT_MAX &&
     877           0 :                    !conn->disk_inflight &&
     878           0 :                    !conn->net_inflight ) ) {
     879           0 :     if( FD_UNLIKELY( conn->sick ) ) {
     880           0 :       conn_close( ctx, conn_idx );
     881           0 :       return;
     882           0 :     }
     883           0 :     conn->state = CONN_STATE_REQ_PEEK;
     884           0 :     prep_peek( ctx, conn_idx );
     885           0 :   }
     886           0 : }
     887             : 
     888             : /* shovel_comp_disk reacts to a snapshot streaming disk read completion. */
     889             : 
     890             : static void
     891             : shovel_comp_disk( fd_snapsv_t * ctx,
     892             :                   uint          conn_idx,
     893           0 :                   int           res ) {
     894           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     895           0 :   FD_CHECK_CRIT( conn->state==CONN_STATE_RES_SHOVEL && conn->disk_inflight,
     896           0 :                  "unexpected shovel disk completion" );
     897           0 :   conn->disk_inflight = 0U;
     898             : 
     899           0 :   ulong rem = conn->snap.range1 - conn->snap.range0;
     900           0 :   if( FD_UNLIKELY( res<=0 || (ulong)res>fd_ulong_min( ctx->iobuf_sz, rem ) ) ) {
     901           0 :     if( res<0 ) FD_LOG_WARNING(( "snapshot read failed (%i-%s)", -res, fd_io_strerror( -res ) ));
     902           0 :     else        FD_LOG_WARNING(( "snapshot file ended early" ));
     903           0 :     conn->closing = 1U;
     904           0 :     shovel( ctx, conn_idx );
     905           0 :     return;
     906           0 :   }
     907             : 
     908           0 :   conn->snap.range0 += (ulong)res;
     909           0 :   conn->rdbuf_len    = (uint)res;
     910           0 :   shovel( ctx, conn_idx );
     911           0 : }
     912             : 
     913             : /* shovel_comp_net reacts to a snapshot streaming network write
     914             :    completion. */
     915             : 
     916             : static void
     917             : shovel_comp_net( fd_snapsv_t * ctx,
     918             :                  uint          conn_idx,
     919           0 :                  int           res ) {
     920           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     921           0 :   FD_CHECK_CRIT( conn->state==CONN_STATE_RES_SHOVEL, "unexpected shovel net completion" );
     922           0 :   FD_CHECK_CRIT( conn->net_inflight, "unexpected SEND completion" );
     923           0 :   conn->net_inflight = 0U;
     924             : 
     925           0 :   if( FD_UNLIKELY( res<=0 || (uint)res>conn->res.len-conn->res.sent ) ) {
     926           0 :     FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
     927           0 :     if( res<0 ) {
     928           0 :       FD_LOG_INFO(( "snapshot download peer %s:%u: snapshot data send error (%i-%s)",
     929           0 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
     930           0 :     }
     931           0 :     conn->closing = 1U;
     932           0 :     shovel( ctx, conn_idx );
     933           0 :     return;
     934           0 :   }
     935             : 
     936           0 :   conn->res.sent += (uint)res;
     937           0 :   FD_MCNT_INC( SNAPSV, BYTES_WRITTEN, (ulong)res );
     938           0 :   shovel( ctx, conn_idx );
     939           0 : }
     940             : 
     941             : /* prep_write_hdr enqueues a header write op. */
     942             : 
     943             : static void
     944             : prep_write_hdr( fd_snapsv_t * ctx,
     945           0 :                 uint          conn_idx ) {
     946           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     947           0 :   fd_io_uring_t * ring = ctx->ring;
     948           0 :   struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ring->sq );
     949           0 :   FD_CHECK_ERR( conn->res.sent < conn->res.len, "conn res state confusion" );
     950           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     951           0 :   *sqe = (struct io_uring_sqe) {
     952           0 :     .opcode    = FD_IORING_OP_SEND,
     953           0 :     .flags     = IOSQE_FIXED_FILE | IOSQE_IO_LINK,
     954             :     //.ioprio    = IORING_RECVSEND_FIXED_BUF,
     955           0 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     956           0 :     .addr      = (ulong)conn_iobuf( ctx, conn ) + conn->res.sent,
     957           0 :     .len       = conn->res.len - conn->res.sent,
     958             :     //.buf_index = 0U,
     959           0 :     .msg_flags = MSG_NOSIGNAL
     960           0 :   };
     961           0 :   snapsv_udata_t udata = { .op = UDATA_OP_WRITE_HDR, .conn_idx = conn_idx };
     962           0 :   sqe->user_data = udata.user_data;
     963             : 
     964           0 :   sqe = fd_io_uring_get_sqe( ring->sq );
     965           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     966           0 :   *sqe = (struct io_uring_sqe) {
     967           0 :     .opcode = FD_IORING_OP_LINK_TIMEOUT,
     968           0 :     .flags  = IOSQE_FIXED_FILE, /* ignored */
     969           0 :     .addr   = (ulong)&ctx->send_timeout,
     970           0 :     .len    = 1U
     971           0 :   };
     972           0 :   udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
     973           0 :   sqe->user_data = udata.user_data;
     974           0 : }
     975             : 
     976             : /* serve_http_err dispatches a RES_WRITE_HDR job for a generic HTTP
     977             :    error page. */
     978             : 
     979             : static void
     980             : serve_http_err( fd_snapsv_t * ctx,
     981           0 :                 uint          conn_idx ) {
     982           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     983           0 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_WRITE_ERR, "state confusion" );
     984           0 :   iobuf_acquire( ctx, &conn->iobuf_idx );
     985           0 :   uchar * iobuf = conn_iobuf( ctx, conn );
     986           0 :   char * p = fd_cstr_init( (char *)iobuf );
     987           0 :   p = fd_cstr_append_cstr( p, "HTTP/1.1 ");
     988           0 :   switch( conn->http_err ) {
     989           0 :   case 400: p = fd_cstr_append_cstr( p, "400 Bad Request"           ); break;
     990           0 :   case 404: p = fd_cstr_append_cstr( p, "404 Not Found"             ); break;
     991           0 :   case 416: p = fd_cstr_append_cstr( p, "416 Range Not Satisfiable" ); break;
     992           0 :   default:  p = fd_cstr_append_cstr( p, "500 Internal Server Error" ); break;
     993           0 :   }
     994           0 :   p = fd_cstr_append_cstr( p, "\r\n" );
     995           0 :   if( conn->http_err==416 && conn->snap.slot ) {
     996           0 :     p = fd_cstr_append_cstr( p, "Content-Range: bytes */" );
     997           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, conn->snap.slot->sz, fd_ulong_base10_dig_cnt( conn->snap.slot->sz ) );
     998           0 :     p = fd_cstr_append_cstr( p, "\r\n" );
     999           0 :   }
    1000           0 :   p = fd_cstr_append_cstr( p, "Content-Length: 0\r\n" );
    1001           0 :   if( conn->sick ) {
    1002           0 :     p = fd_cstr_append_cstr( p, "Connection: close\r\n" );
    1003           0 :   }
    1004           0 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1005           0 :   conn->res.sent = 0;
    1006           0 :   conn->res.len  = (uint)( p - (char *)iobuf );
    1007           0 :   prep_write_hdr( ctx, conn_idx );
    1008           0 : }
    1009             : 
    1010             : /* serve_snap_res_hdr dispatches a RES_WRITE_HDR job for a snapshot GET
    1011             :    or HEAD request. */
    1012             : 
    1013             : static void
    1014             : serve_snap_res_hdr( fd_snapsv_t * ctx,
    1015           0 :                     uint          conn_idx ) {
    1016           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1017           0 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_WRITE_HDR, "state confusion" );
    1018             : 
    1019           0 :   snap_entry_t * snap = conn_snap_entry( conn );
    1020           0 :   if( FD_UNLIKELY( !snap ) ) {
    1021             :     /* rare edge case: snap was deleted by the snapmk tile just after
    1022             :        the user requested it.  Don't bother returning an error, just
    1023             :        abort the conn. */
    1024           0 :     conn_close( ctx, conn_idx );
    1025           0 :     return;
    1026           0 :   }
    1027           0 :   if( FD_UNLIKELY( !snap_lock( snap ) ) ) {
    1028           0 :     conn_close( ctx, conn_idx );
    1029           0 :     return;
    1030           0 :   }
    1031             :   /* return response header */
    1032           0 :   FD_CHECK_ERR( conn->iobuf_idx==UINT_MAX, "conn has stale iobuf" );
    1033           0 :   iobuf_acquire( ctx, &conn->iobuf_idx );
    1034           0 :   uchar * iobuf = conn_iobuf( ctx, conn );
    1035           0 :   char * p = fd_cstr_init( (char *)iobuf );
    1036           0 :   if( conn->range ) {
    1037           0 :     p = fd_cstr_append_cstr( p, "HTTP/1.1 206 Partial Content\r\n" );
    1038           0 :   } else {
    1039           0 :     p = fd_cstr_append_cstr( p, "HTTP/1.1 200 OK\r\n" );
    1040           0 :   }
    1041           0 :   if( snap->is_zstd ) {
    1042           0 :     p = fd_cstr_append_cstr( p, "Content-Type: application/zstd\r\n" );
    1043           0 :   } else {
    1044           0 :     p = fd_cstr_append_cstr( p, "Content-Type: application/x-tar\r\n" );
    1045           0 :   }
    1046           0 :   p = fd_cstr_append_cstr( p, "Accept-Ranges: bytes\r\n" );
    1047           0 :   if( conn->range ) {
    1048           0 :     p = fd_cstr_append_cstr( p, "Content-Range: bytes " );
    1049           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, conn->snap.range0, fd_ulong_base10_dig_cnt( conn->snap.range0 ) );
    1050           0 :     p = fd_cstr_append_char( p, '-' );
    1051           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, conn->snap.range1-1UL, fd_ulong_base10_dig_cnt( conn->snap.range1-1UL ) );
    1052           0 :     p = fd_cstr_append_char( p, '/' );
    1053           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, snap->sz, fd_ulong_base10_dig_cnt( snap->sz ) );
    1054           0 :     p = fd_cstr_append_cstr( p, "\r\n" );
    1055           0 :   }
    1056           0 :   p = fd_cstr_append_cstr( p, "Content-Length: " );
    1057           0 :   ulong content_len = conn->snap.range1 - conn->snap.range0;
    1058           0 :   p = fd_cstr_append_ulong_as_text( p, 0, 0, content_len, fd_ulong_base10_dig_cnt( content_len ) );
    1059           0 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1060           0 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1061           0 :   conn->res.sent = 0;
    1062           0 :   conn->res.len  = (uint)( p - (char *)iobuf );
    1063           0 :   prep_write_hdr( ctx, conn_idx );
    1064           0 : }
    1065             : 
    1066             : /* newest_snap returns the newest snapshot of the requested type. */
    1067             : 
    1068             : static snap_entry_t *
    1069             : newest_snap( fd_snapsv_t * ctx,
    1070           0 :              int           incremental ) {
    1071             : 
    1072           0 :   if( FD_UNLIKELY( !( incremental ? ctx->snap_cnt_incr : ctx->snap_cnt_full ) ) ) {
    1073           0 :     return NULL; /* no such snapshot of this type */
    1074           0 :   }
    1075           0 :   snap_entry_t * entry = incremental ? ctx->newest_incr : ctx->newest_full;
    1076           0 :   if( FD_UNLIKELY( !entry ) ) {
    1077           0 :     snap_entry_t * entry0   = snap_map_ele0( ctx->snap_map );
    1078           0 :     ulong          ele_max  = snap_map_ele_max( ctx->snap_map );
    1079           0 :     for( ulong i=0UL; i<ele_max; i++ ) {
    1080           0 :       snap_entry_t * e = &entry0[ i ];
    1081           0 :       if( snap_map_ele_is_free( e ) ) continue;
    1082           0 :       if( (e->key.base_slot!=ULONG_MAX)!=!!incremental ) continue;
    1083           0 :       if( entry && e->key.slot<=entry->key.slot ) continue;
    1084           0 :       entry = e;
    1085           0 :     }
    1086           0 :   }
    1087           0 :   *( incremental ? &ctx->newest_incr : &ctx->newest_full ) = entry;
    1088           0 :   if( FD_UNLIKELY( !entry ) ) return NULL;
    1089           0 :   return entry;
    1090           0 : }
    1091             : 
    1092             : /* serve_redirect dispatches a RES_WRITE_HDR job for a /snapshot.tar.bz2
    1093             :    redirect. */
    1094             : 
    1095             : static void
    1096             : serve_redirect( fd_snapsv_t * ctx,
    1097           0 :                 uint          conn_idx ) {
    1098           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1099           0 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_REDIRECT, "state confusion" );
    1100             : 
    1101           0 :   snap_entry_t * snap = newest_snap( ctx, conn->incremental );
    1102           0 :   if( FD_UNLIKELY( !snap ) ) {
    1103           0 :     conn->state    = CONN_STATE_RES_WRITE_ERR;
    1104           0 :     conn->http_err = 404U;
    1105           0 :     serve_http_err( ctx, conn_idx );
    1106           0 :     return;
    1107           0 :   }
    1108             : 
    1109             :   /* return response */
    1110           0 :   FD_CHECK_ERR( conn->iobuf_idx==UINT_MAX, "conn has stale iobuf" );
    1111           0 :   iobuf_acquire( ctx, &conn->iobuf_idx );
    1112           0 :   uchar * iobuf = conn_iobuf( ctx, conn );
    1113           0 :   char * p = fd_cstr_init( (char *)iobuf );
    1114           0 :   p = fd_cstr_append_cstr( p, "HTTP/1.1 302 Found\r\n" );
    1115           0 :   p = fd_cstr_append_cstr( p, "Location: /" );
    1116           0 :   if( conn->incremental ) {
    1117           0 :     p = fd_cstr_append_cstr( p, "incremental-snapshot-" );
    1118           0 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, snap->key.base_slot, fd_ulong_base10_dig_cnt( snap->key.base_slot ) );
    1119           0 :     p = fd_cstr_append_char( p, '-' );
    1120           0 :   } else {
    1121           0 :     p = fd_cstr_append_cstr( p, "snapshot-" );
    1122           0 :   }
    1123           0 :   p = fd_cstr_append_ulong_as_text( p, 0, 0, snap->key.slot, fd_ulong_base10_dig_cnt( snap->key.slot ) );
    1124           0 :   p = fd_cstr_append_char( p, '-' );
    1125           0 :   char hash_b58[ FD_BASE58_ENCODED_32_SZ ];
    1126           0 :   fd_base58_encode_32( snap->hash, NULL, hash_b58 );
    1127           0 :   p = fd_cstr_append_cstr( p, hash_b58 );
    1128           0 :   p = fd_cstr_append_cstr( p, snap->is_zstd ? ".tar.zst\r\n" : ".tar\r\n" );
    1129           0 :   p = fd_cstr_append_cstr( p, "Content-Length: 0\r\n" );
    1130           0 :   if( conn->sick ) {
    1131           0 :     p = fd_cstr_append_cstr( p, "Connection: close\r\n" );
    1132           0 :   }
    1133           0 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1134           0 :   conn->res.sent = 0;
    1135           0 :   conn->res.len  = (uint)( p - (char *)iobuf );
    1136           0 :   conn->state = CONN_STATE_RES_WRITE_HDR; /* shared completion path */
    1137           0 :   prep_write_hdr( ctx, conn_idx );
    1138           0 : }
    1139             : 
    1140             : static void
    1141             : prep_peek( fd_snapsv_t * ctx,
    1142             :            uint          conn_idx );
    1143             : 
    1144             : /* handle_write_hdr_comp handles the completion of a header write op. */
    1145             : 
    1146             : static void
    1147             : handle_write_hdr_comp( fd_snapsv_t * ctx,
    1148             :                        uint          conn_idx,
    1149           0 :                        int           res ) {
    1150           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1151           0 :   if( FD_UNLIKELY( res<0 ) ) {
    1152           0 :     if( res<0 ) {
    1153           0 :       FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
    1154           0 :       FD_LOG_INFO(( "snapshot download peer %s:%u: response header send error (%i-%s)",
    1155           0 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
    1156           0 :     }
    1157           0 :     conn_close( ctx, conn_idx );
    1158           0 :     return;
    1159           0 :   }
    1160           0 :   conn->res.sent += (uint)res;
    1161           0 :   if( FD_LIKELY( conn->res.sent < conn->res.len ) ) {
    1162           0 :     prep_write_hdr( ctx, conn_idx );
    1163           0 :     return;
    1164           0 :   }
    1165             :   /* wrote response body */
    1166           0 :   iobuf_release( ctx, &conn->iobuf_idx );
    1167           0 :   if( FD_UNLIKELY( conn->sick ) ) {
    1168           0 :     conn_close( ctx, conn_idx );
    1169           0 :     return;
    1170           0 :   }
    1171           0 :   switch( conn->state ) {
    1172           0 :   case CONN_STATE_RES_WRITE_ERR:
    1173             :     /* returned an error, handle the next request */
    1174           0 :     conn->state = CONN_STATE_REQ_PEEK;
    1175           0 :     prep_peek( ctx, conn_idx );
    1176           0 :     return;
    1177           0 :   case CONN_STATE_RES_WRITE_HDR:
    1178           0 :     if( conn->head ) {
    1179           0 :       conn->state = CONN_STATE_REQ_PEEK;
    1180           0 :       prep_peek( ctx, conn_idx );
    1181           0 :       return;
    1182           0 :     }
    1183             :     /* now serve the snapshot body */
    1184           0 :     conn->state = CONN_STATE_RES_SHOVEL;
    1185           0 :     shovel( ctx, conn_idx );
    1186           0 :     return;
    1187           0 :   default:
    1188           0 :     FD_LOG_CRIT(( "conn %u: state confusion", conn_idx ));
    1189           0 :   }
    1190           0 : }
    1191             : 
    1192             : /* match_snapshot_path parses a snapshot HTTP request path. */
    1193             : 
    1194             : static snap_key_t *
    1195             : match_snapshot_path( char const * path,
    1196             :                      ulong        path_len,
    1197             :                      snap_key_t * out,
    1198             :                      uchar        out_hash[ 32 ],
    1199           0 :                      int *        out_is_zstd ) {
    1200           0 :   if( path_len<9 ) return NULL;
    1201             : 
    1202             :   /* ugly: copy to cstr */
    1203           0 :   char cstr[ FD_SNAP_NAME_MAX ];
    1204           0 :   if( FD_UNLIKELY( path_len>=FD_SNAP_NAME_MAX-1 ) ) return NULL;
    1205           0 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( cstr ), path, path_len ) );
    1206             : 
    1207           0 :   ulong full_slot, incremental_slot;
    1208           0 :   int res = fd_ssarchive_parse_filename( cstr, &full_slot, &incremental_slot, out_hash, out_is_zstd );
    1209           0 :   if( FD_UNLIKELY( res!=0 ) ) return NULL;
    1210           0 :   *out = (snap_key_t) {
    1211           0 :     .slot      = incremental_slot!=ULONG_MAX ? incremental_slot : full_slot,
    1212           0 :     .base_slot = incremental_slot!=ULONG_MAX ? full_slot : ULONG_MAX
    1213           0 :   };
    1214           0 :   return out;
    1215           0 : }
    1216             : 
    1217             : /* parse_range_header parses a 'Range: bytes=start-end' header.
    1218             :    value points to the header value char[value_len].
    1219             :    object_sz is the size of the file, and [*out_range0, *out_range1)
    1220             :    is the requested range. */
    1221             : 
    1222             : static int
    1223             : parse_range_header( char const * value,
    1224             :                     ulong        value_len,
    1225             :                     ulong        object_sz,
    1226             :                     ulong *      out_range0,
    1227           0 :                     ulong *      out_range1 ) {
    1228           0 :   char const * p   = value;
    1229           0 :   char const * end = value + value_len;
    1230             : 
    1231           0 :   while( p<end && (*p==' ' || *p=='\t') ) p++;
    1232           0 :   while( end>p && (end[-1]==' ' || end[-1]=='\t') ) end--;
    1233           0 :   if( FD_UNLIKELY( (ulong)(end-p)<7UL || strncasecmp( p, "bytes=", 6UL ) ) ) return -1;
    1234           0 :   p += 6;
    1235             : 
    1236           0 :   ulong first = 0UL;
    1237           0 :   int have_first = 0;
    1238           0 :   while( p<end && *p>='0' && *p<='9' ) {
    1239           0 :     uint digit = (uint)(*p-'0');
    1240           0 :     if( FD_UNLIKELY( first>(ULONG_MAX-digit)/10UL ) ) return -1;
    1241           0 :     first = first*10UL + digit;
    1242           0 :     have_first = 1;
    1243           0 :     p++;
    1244           0 :   }
    1245           0 :   if( FD_UNLIKELY( p==end || *p!='-' ) ) return -1;
    1246           0 :   p++;
    1247             : 
    1248           0 :   ulong last = 0UL;
    1249           0 :   int have_last = 0;
    1250           0 :   while( p<end && *p>='0' && *p<='9' ) {
    1251           0 :     uint digit = (uint)(*p-'0');
    1252           0 :     if( FD_UNLIKELY( last>(ULONG_MAX-digit)/10UL ) ) return -1;
    1253           0 :     last = last*10UL + digit;
    1254           0 :     have_last = 1;
    1255           0 :     p++;
    1256           0 :   }
    1257           0 :   if( FD_UNLIKELY( p!=end || (!have_first && !have_last) ) ) return -1;
    1258           0 :   if( FD_UNLIKELY( !object_sz ) ) return -2;
    1259             : 
    1260           0 :   if( !have_first ) {
    1261           0 :     if( FD_UNLIKELY( !last ) ) return -2;
    1262           0 :     *out_range0 = last<object_sz ? object_sz-last : 0UL;
    1263           0 :     *out_range1 = object_sz;
    1264           0 :     return 0;
    1265           0 :   }
    1266             : 
    1267           0 :   if( FD_UNLIKELY( first>=object_sz ) ) return -2;
    1268           0 :   if( FD_UNLIKELY( have_last && last<first ) ) return -2;
    1269           0 :   *out_range0 = first;
    1270           0 :   *out_range1 = have_last && last<object_sz-1UL ? last+1UL : object_sz;
    1271           0 :   return 0;
    1272           0 : }
    1273             : 
    1274             : /* handle_peek handles newly received TCP data.  peek_len bytes of
    1275             :    unconsumed request head sit at conn->req_len in the iobuf. */
    1276             : 
    1277             : static void
    1278             : handle_peek( fd_snapsv_t * ctx,
    1279             :              uint          conn_idx,
    1280           0 :              int           res ) {
    1281           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1282           0 :   if( FD_UNLIKELY( res<=0 ) ) {
    1283           0 :     if( FD_UNLIKELY( res<0 ) ) {
    1284           0 :       FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
    1285           0 :       FD_LOG_INFO(( "snapshot download peer %s:%u: receive error (%i-%s)",
    1286           0 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
    1287           0 :     }
    1288           0 :     conn_close( ctx, conn_idx );
    1289           0 :     return;
    1290           0 :   }
    1291           0 :   uint peek_len = (uint)res;
    1292             : 
    1293           0 :   FD_MCNT_INC( SNAPSV, BYTES_READ, (ulong)res );
    1294             : 
    1295           0 :   char const * method;  ulong method_len;
    1296           0 :   char const * path;    ulong path_len;
    1297           0 :   int minor_version;
    1298           0 :   struct phr_header headers[ REQ_HEADER_MAX ];
    1299           0 :   ulong header_cnt = REQ_HEADER_MAX;
    1300           0 :   int parsed = phr_parse_request(
    1301           0 :       (char const *)conn_iobuf( ctx, conn ),
    1302           0 :       conn->req.len + peek_len,
    1303           0 :       &method, &method_len,
    1304           0 :       &path,   &path_len,
    1305           0 :       &minor_version,
    1306           0 :       headers, &header_cnt,
    1307           0 :       conn->req.len /* slowloris defense */
    1308           0 :   );
    1309             : 
    1310           0 :   if( FD_UNLIKELY( parsed==-2 ) ) { /* request head incomplete */
    1311           0 :     if( FD_UNLIKELY( conn->req.len+peek_len >= ctx->iobuf_sz ) ) {
    1312           0 :       FD_LOG_DEBUG(( "conn %u: request head exceeds %u bytes", conn_idx, ctx->iobuf_sz ));
    1313           0 :       conn_close( ctx, conn_idx );
    1314           0 :       return;
    1315           0 :     }
    1316             :     /* avoid copying the same data again */
    1317           0 :     prep_consume( ctx, conn_idx, peek_len, UDATA_OP_CONSUME_FRAG );
    1318           0 :     conn->state = CONN_STATE_REQ_SKIP;
    1319           0 :     return;
    1320           0 :   }
    1321             : 
    1322           0 :   if( FD_UNLIKELY( parsed<0 ) ) { /* malformed request head */
    1323           0 :     FD_LOG_DEBUG(( "conn %u: malformed request", conn_idx ));
    1324           0 :     conn_close( ctx, conn_idx );
    1325           0 :     return;
    1326           0 :   }
    1327             : 
    1328             :   /* valid request at this point */
    1329           0 :   FD_CHECK_ERR( (uint)parsed > conn->req.len, "request head shrank" );
    1330           0 :   prep_consume( ctx, conn_idx, (uint)parsed - conn->req.len, UDATA_OP_CONSUME_TAIL );
    1331           0 :   conn->state   = CONN_STATE_REQ_DONE;
    1332           0 :   conn->req.len = (uint)parsed - conn->req.len;
    1333             : 
    1334             :   /* must not do any work until CONSUME_TAIL completes,
    1335             :      so remember what to do for when that happens */
    1336           0 :   snap_key_t query;
    1337           0 :   uchar      query_hash[ 32 ];
    1338           0 :   int        query_is_zstd;
    1339           0 :   if( method_len==4UL && !memcmp( method, "HEAD", method_len ) ) {
    1340           0 :     conn->head = 1;
    1341           0 :   } else if( method_len==3UL && !memcmp( method, "GET", method_len ) ) {
    1342           0 :     conn->head = 0;
    1343           0 :   } else {
    1344           0 :     conn->state    = CONN_STATE_RES_WRITE_ERR;
    1345           0 :     conn->http_err = 500;
    1346           0 :     conn->sick     = 1;
    1347           0 :     return;
    1348           0 :   }
    1349             : 
    1350             :   /* strip leading slashes */
    1351           0 :   if( FD_UNLIKELY( path_len<10    ) ) goto not_found;
    1352           0 :   if( FD_UNLIKELY( path[ 0 ]!='/' ) ) goto not_found;
    1353           0 :   path++; path_len--;
    1354             :   /* skip superfluous leading slashes to help buggy clients */
    1355           0 :   for( ulong i=0UL; path_len && i<15; i++ ) {
    1356           0 :     if( FD_LIKELY( path[ 0 ]!='/' ) ) break;
    1357           0 :     path++; path_len--;
    1358           0 :   }
    1359             : 
    1360           0 :   if( path_len==16 ) {
    1361           0 :     if( !memcmp( path, "snapshot.tar.zst", 16UL ) ||
    1362           0 :         !memcmp( path, "snapshot.tar.bz2", 16UL ) ) {
    1363           0 :       conn->state       = CONN_STATE_RES_REDIRECT;
    1364           0 :       conn->incremental = 0;
    1365           0 :       return;
    1366           0 :     }
    1367           0 :   } else if( path_len==28 ) {
    1368           0 :     if( !memcmp( path, "incremental-snapshot.tar.bz2", 28UL ) ||
    1369           0 :         !memcmp( path, "incremental-snapshot.tar.zst", 28UL ) ) {
    1370           0 :       conn->state       = CONN_STATE_RES_REDIRECT;
    1371           0 :       conn->incremental = 1;
    1372           0 :       return;
    1373           0 :     }
    1374           0 :   }
    1375             : 
    1376             :   /* found a snapshot? */
    1377           0 :   if( match_snapshot_path( path, path_len, &query, query_hash, &query_is_zstd ) ) {
    1378           0 :     snap_entry_t * entry = snap_map_update( ctx->snap_map, &query );
    1379           0 :     if( FD_UNLIKELY( !entry ) ) goto not_found;
    1380           0 :     if( FD_UNLIKELY( 0!=memcmp( entry->hash, query_hash, 32UL ) ) ) goto not_found;
    1381           0 :     if( FD_UNLIKELY( entry->is_zstd!=query_is_zstd ) ) goto not_found;
    1382           0 :     conn->state       = CONN_STATE_RES_WRITE_HDR;
    1383           0 :     conn->snap.key    = query;
    1384           0 :     conn->snap.slot   = entry;
    1385           0 :     conn->range       = 0U;
    1386           0 :     conn->snap.range0 = 0UL;
    1387           0 :     conn->snap.range1 = entry->sz;
    1388           0 :     conn->incremental = entry->key.base_slot!=ULONG_MAX;
    1389             : 
    1390           0 :     char const * range     = NULL;
    1391           0 :     ulong        range_len = 0UL;
    1392           0 :     for( ulong i=0UL; i<header_cnt; i++ ) {
    1393           0 :       if( headers[ i ].name_len==5UL && !strncasecmp( headers[ i ].name, "range", 5UL ) ) {
    1394           0 :         if( !conn->head ) { /* range header only valid for GET request */
    1395           0 :           range     = headers[ i ].value;
    1396           0 :           range_len = headers[ i ].value_len;
    1397           0 :         }
    1398           0 :       }
    1399           0 :     }
    1400           0 :     if( range ) {
    1401           0 :       int range_err = parse_range_header( range, range_len, entry->sz, &conn->snap.range0, &conn->snap.range1 );
    1402           0 :       if( FD_UNLIKELY( range_err ) ) {
    1403           0 :         conn->state    = CONN_STATE_RES_WRITE_ERR;
    1404           0 :         conn->http_err = (uint)( range_err==-2 ? 416 : 400 );
    1405           0 :       } else {
    1406           0 :         conn->range = 1U;
    1407           0 :       }
    1408           0 :     }
    1409           0 :     return;
    1410           0 :   }
    1411             : 
    1412           0 : not_found:
    1413           0 :   conn->state    = CONN_STATE_RES_WRITE_ERR;
    1414           0 :   conn->http_err = 404;
    1415             : 
    1416             :   /* next step happens at handle_consume_tail_comp */
    1417           0 : }
    1418             : 
    1419             : /* handle_consume_frag_comp handles the completion of a CONSUME_FRAG op. */
    1420             : 
    1421             : static void
    1422             : handle_consume_frag_comp( fd_snapsv_t * ctx,
    1423             :                           uint          conn_idx,
    1424           0 :                           int           res ) {
    1425           0 :   if( FD_UNLIKELY( res<=0 ) ) {
    1426           0 :     if( FD_UNLIKELY( res<0 ) ) {
    1427           0 :       FD_LOG_DEBUG(( "conn %u: recv() failed (%i-%s)", conn_idx, -res, fd_io_strerror( -res ) ));
    1428           0 :     }
    1429           0 :     conn_close( ctx, conn_idx );
    1430           0 :     return;
    1431           0 :   }
    1432           0 :   uint consume_len = (uint)res;
    1433           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1434           0 :   FD_CHECK_ERR( conn->state==CONN_STATE_REQ_SKIP, "conn state confusion" );
    1435           0 :   conn->req.len += consume_len;
    1436           0 :   prep_peek( ctx, conn_idx );
    1437           0 : }
    1438             : 
    1439             : /* handle_consume_tail_comp handles the completion of a CONSUME_TAIL op. */
    1440             : 
    1441             : static void
    1442             : handle_consume_tail_comp( fd_snapsv_t * ctx,
    1443             :                           uint          conn_idx,
    1444           0 :                           int           res ) {
    1445           0 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1446           0 :   if( FD_UNLIKELY( res!=(int)conn->req.len ) ) {
    1447           0 :     conn_close( ctx, conn_idx );
    1448           0 :     return;
    1449           0 :   }
    1450           0 :   conn->req.len = 0;
    1451           0 :   conn->request_start_nanos = 0L;
    1452           0 :   iobuf_release( ctx, &conn->iobuf_idx );
    1453           0 :   switch( ctx->conn0[ conn_idx ].state ) {
    1454           0 :   case CONN_STATE_RES_WRITE_ERR:
    1455           0 :     serve_http_err( ctx, conn_idx );
    1456           0 :     break;
    1457           0 :   case CONN_STATE_RES_WRITE_HDR:
    1458           0 :     serve_snap_res_hdr( ctx, conn_idx );
    1459           0 :     break;
    1460           0 :   case CONN_STATE_RES_REDIRECT:
    1461           0 :     serve_redirect( ctx, conn_idx );
    1462           0 :     break;
    1463           0 :   default:
    1464           0 :     FD_LOG_CRIT(( "conn %u: state confusion", conn_idx ));
    1465           0 :   }
    1466           0 : }
    1467             : 
    1468             : /* futex_prep asks the kernel to send a CQE for when a new
    1469             :    snapmk_out frag becomes available. */
    1470             : 
    1471             : static int
    1472             : futex_prep( fd_snapsv_t * ctx,
    1473           0 :             ulong         in_idx ) {
    1474           0 :   ulong seq_prod = fd_mcache_seq_query( ctx->in[ in_idx ].seq_prod );
    1475           0 :   ulong seq_next = fd_seq_inc( ctx->in[ in_idx ].seq_cons, 1UL );
    1476           0 :   fd_frag_meta_t const * mline = ctx->in[ in_idx ].mcache + fd_mcache_line_idx( seq_next, ctx->in[ in_idx ].depth );
    1477           0 :   if( FD_UNLIKELY( fd_frag_meta_seq_query( mline )==seq_next ) ) return 0;
    1478             : 
    1479           0 :   if( FD_LIKELY( ctx->in[ in_idx ].futex_armed ) ) return 1;
    1480           0 :   fd_io_uring_t * ring = ctx->ring;
    1481           0 :   struct io_uring_sqe * sqe = fd_io_uring_get_sqe( ring->sq );
    1482           0 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1483           0 :   *sqe = (struct io_uring_sqe) {
    1484           0 :     .opcode = FD_IORING_OP_FUTEX_WAIT,
    1485           0 :     .flags  = IOSQE_FIXED_FILE, /* ignored, required for sandbox (hope no kernel ABI breakage) */
    1486           0 :     .fd     = FUTEX2_SIZE_U32,
    1487           0 :     .addr   = (ulong)ctx->in[ in_idx ].seq_prod,
    1488           0 :     .off    = (ulong)(uint)seq_prod, /* value to compare against */
    1489           0 :     .addr3  = FUTEX_BITSET_MATCH_ANY
    1490           0 :   };
    1491           0 :   snapsv_udata_t udata = { .op = UDATA_OP_FUTEX, .conn_idx = (uint)in_idx };
    1492           0 :   sqe->user_data = udata.user_data;
    1493           0 :   ctx->in[ in_idx ].futex_armed = 1;
    1494           0 :   return 1;
    1495           0 : }
    1496             : 
    1497             : /* futex_comp is called when a snapmk_out frag was detected.  Such
    1498             :    frags are also polled by the event loop, but this is required to wake
    1499             :    up fast from an io_uring_enter sleep. */
    1500             : 
    1501             : static void
    1502             : futex_comp( fd_snapsv_t * ctx,
    1503             :             uint          in_idx,
    1504           0 :             int           res ) {
    1505           0 :   FD_CHECK_CRIT( in_idx<IN_LINK_MAX, "io_uring completion has invalid in index" );
    1506           0 :   ctx->in[ in_idx ].futex_armed = 0;
    1507           0 :   if( FD_UNLIKELY( res<0 && res!=-EAGAIN && res!=-ECANCELED && res!=-EINTR ) ) {
    1508           0 :     FD_LOG_ERR(( "IORING_OP_FUTEX_WAIT failed (%i-%s)", -res, fd_io_strerror( -res ) ));
    1509           0 :   }
    1510           0 : }
    1511             : 
    1512             : /* handle_cqe handles a single io_uring completion. */
    1513             : 
    1514             : static void
    1515             : handle_cqe( fd_snapsv_t *               ctx,
    1516           0 :             struct io_uring_cqe const * cqe ) {
    1517           0 :   snapsv_udata_t udata = { .user_data = cqe->user_data };
    1518           0 :   switch( udata.op ) {
    1519           0 :   case UDATA_OP_ACCEPT:
    1520           0 :     handle_accept( ctx, udata.conn_idx, cqe->res );
    1521           0 :     break;
    1522           0 :   case UDATA_OP_PEEK:
    1523           0 :     handle_peek( ctx, udata.conn_idx, cqe->res );
    1524           0 :     break;
    1525           0 :   case UDATA_OP_CONSUME_FRAG:
    1526           0 :     handle_consume_frag_comp( ctx, udata.conn_idx, cqe->res );
    1527           0 :     break;
    1528           0 :   case UDATA_OP_CONSUME_TAIL:
    1529           0 :     handle_consume_tail_comp( ctx, udata.conn_idx, cqe->res );
    1530           0 :     break;
    1531           0 :   case UDATA_OP_WRITE_HDR:
    1532           0 :     handle_write_hdr_comp( ctx, udata.conn_idx, cqe->res );
    1533           0 :     break;
    1534           0 :   case UDATA_OP_SHOVEL_DISK:
    1535           0 :     shovel_comp_disk( ctx, udata.conn_idx, cqe->res );
    1536           0 :     break;
    1537           0 :   case UDATA_OP_SHOVEL_NET:
    1538           0 :     shovel_comp_net( ctx, udata.conn_idx, cqe->res );
    1539           0 :     break;
    1540           0 :   case UDATA_OP_FUTEX:
    1541           0 :     futex_comp( ctx, udata.conn_idx, cqe->res );
    1542           0 :     break;
    1543           0 :   case UDATA_OP_TIMEOUT:
    1544           0 :     break;
    1545           0 :   default:
    1546           0 :     FD_LOG_CRIT(( "io_uring completion has invalid user_data 0x%016lx", (ulong)cqe->user_data ));
    1547           0 :   }
    1548           0 : }
    1549             : 
    1550             : /* stem_in_update grants flow control credits to an upstream producer. */
    1551             : 
    1552             : static inline void
    1553             : stem_in_update( fd_stem_tile_in_t * in );
    1554             : 
    1555             : /* after_credit performs one io_uring event-loop iteration. */
    1556             : 
    1557             : static void
    1558             : after_credit( fd_snapsv_t *       ctx,
    1559             :               fd_stem_context_t * stem,
    1560             :               int *               opt_poll_in,
    1561           0 :               int *               charge_busy ) {
    1562           0 :   (void)stem; (void)opt_poll_in;
    1563           0 :   fd_io_uring_t * ring = ctx->ring;
    1564             : 
    1565             :   /* Reap background completions */
    1566           0 :   uint ready = fd_io_uring_cq_ready( ring->cq );
    1567           0 :   for( uint i=0U; i<ready; i++ ) {
    1568           0 :     handle_cqe( ctx, fd_io_uring_cq_head( ring->cq ) );
    1569           0 :     fd_io_uring_cq_advance( ring->cq, 1U );
    1570           0 :     *charge_busy = 1;
    1571           0 :   }
    1572             : 
    1573             :   /* Prepare for sleep */
    1574           0 :   int waiting = !!futex_prep( ctx, 0UL );
    1575           0 :   stem_in_update( &stem->in[ 0 ] );
    1576             : 
    1577             :   /* Dispatch io_uring work, poll completions, and do a bounded sleep */
    1578           0 :   uint tail = ring->sq->sqe_tail;
    1579           0 :   atomic_store_explicit( ring->sq->ktail, tail, memory_order_release );
    1580           0 :   uint head = atomic_load_explicit( ring->sq->khead, memory_order_relaxed );
    1581           0 :   ring->sq->sqe_head = head;
    1582           0 :   uint to_submit = tail - head;
    1583           0 :   struct __kernel_timespec timeout = { .tv_nsec = (long)50e6L }; /* 50ms */
    1584           0 :   struct io_uring_getevents_arg enter_arg = { .ts = (ulong)&timeout };
    1585           0 :   int submitted = fd_io_uring_enter(
    1586           0 :       ring->ioring_fd, to_submit, !!waiting,
    1587           0 :       IORING_ENTER_GETEVENTS | IORING_ENTER_EXT_ARG,
    1588           0 :       &enter_arg, sizeof(enter_arg)
    1589           0 :   );
    1590           0 :   if( FD_UNLIKELY( submitted<0 ) ) {
    1591           0 :     if( FD_LIKELY( errno==ETIME ) ) {
    1592           0 :       submitted = 0; /* timeout, do housekeeping */
    1593           0 :     } else if( FD_LIKELY( errno==EINTR ) ) {
    1594           0 :       *charge_busy = !!submitted;
    1595           0 :       return;
    1596           0 :     } else {
    1597           0 :       FD_LOG_ERR(( "io_uring_enter failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    1598           0 :     }
    1599           0 :   }
    1600           0 :   if( FD_UNLIKELY( fd_io_uring_sq_dropped( ring->sq ) ) ) {
    1601           0 :     FD_LOG_ERR(( "io_uring submission queue dropped entries" ));
    1602           0 :   }
    1603           0 :   if( FD_UNLIKELY( fd_io_uring_cq_overflow( ring->cq ) ) ) {
    1604           0 :     FD_LOG_ERR(( "io_uring completion queue overflowed" ));
    1605           0 :   }
    1606             : 
    1607             :   /* Reap deferred/immediate completions */
    1608           0 :   ready = fd_io_uring_cq_ready( ring->cq );
    1609           0 :   for( uint i=0U; i<ready; i++ ) {
    1610           0 :     handle_cqe( ctx, fd_io_uring_cq_head( ring->cq ) );
    1611           0 :     fd_io_uring_cq_advance( ring->cq, 1U );
    1612           0 :     *charge_busy = 1;
    1613           0 :   }
    1614           0 : }
    1615             : 
    1616             : /* snap_open opens the given snapshot by pool index.  Silently ignores
    1617             :    ENOENT (rare race condition where the snapshot is immediately
    1618             :    deleted after being created.) */
    1619             : 
    1620             : static void
    1621             : snap_open( fd_snapsv_t * ctx,
    1622             :            ulong         slot,
    1623             :            ulong         base_slot,
    1624             :            ulong         pool_idx,
    1625             :            ulong         sz,
    1626           0 :            char const *  name ) {
    1627           0 :   FD_CHECK_ERR( ctx->snap_cnt_full + ctx->snap_cnt_incr < ctx->snap_max, "too many snapshot files (snapmk_out desync?)" );
    1628           0 :   FD_CHECK_CRIT( pool_idx < ctx->snap_max, "invalid snapshot pool index" );
    1629             : 
    1630           0 :   ulong parsed_full_slot;
    1631           0 :   ulong parsed_incremental_slot;
    1632           0 :   uchar hash[ 32 ];
    1633           0 :   int   is_zstd;
    1634           0 :   FD_CHECK_ERR( !fd_ssarchive_parse_filename( name, &parsed_full_slot, &parsed_incremental_slot, hash, &is_zstd ),
    1635           0 :                 "invalid snapshot filename" );
    1636             : 
    1637           0 :   snap_entry_t * entry = snap_map_insert( ctx->snap_map, &(snap_key_t){slot, base_slot} );
    1638           0 :   FD_CHECK_ERR( !!entry, "snap_map_insert failed (snapmk_out desync?)" );
    1639             : 
    1640           0 :   memcpy( entry->hash, hash, sizeof(entry->hash) );
    1641           0 :   entry->sz     = sz;
    1642           0 :   entry->fd     = FD_SNAP_RO_FD( pool_idx );
    1643           0 :   entry->locked = 0;
    1644           0 :   entry->is_zstd = !!is_zstd;
    1645           0 :   if( base_slot==ULONG_MAX ) {
    1646           0 :     ctx->snap_cnt_full++;
    1647           0 :     ctx->newest_full = NULL;
    1648           0 :   } else {
    1649           0 :     ctx->snap_cnt_incr++;
    1650           0 :     ctx->newest_incr = NULL;
    1651           0 :   }
    1652           0 : }
    1653             : 
    1654             : /* snap_lock tries to acquire a read-lock to a snapshot file handle.
    1655             :    Returns 1 on success, 0 on failure (currently write locked).
    1656             :    Snapshots deliberately remain locked until the snapshot is about to
    1657             :    be deleted to prevent F_SETLK churn. */
    1658             : 
    1659             : FD_FN_UNUSED static int
    1660           0 : snap_lock( snap_entry_t * entry ) {
    1661           0 :   if( entry->locked ) return 1; /* already locked */
    1662           0 :   struct flock lock = {
    1663           0 :     .l_type   = F_RDLCK,
    1664           0 :     .l_whence = SEEK_SET
    1665           0 :   };
    1666           0 :   if( FD_UNLIKELY( fcntl( entry->fd, F_SETLK, &lock ) ) ) {
    1667           0 :     if( errno==EAGAIN || errno==EACCES ) return 0; /* write locked */
    1668           0 :     FD_LOG_ERR(( "fcntl(F_RDLCK, %lu, %lu) failed (%i-%s)", entry->key.slot, entry->key.base_slot, errno, fd_io_strerror( errno ) ));
    1669           0 :   }
    1670           0 :   entry->locked = 1;
    1671           0 :   return 1;
    1672           0 : }
    1673             : 
    1674             : /* snap_close releases a snapshot file handle. */
    1675             : 
    1676             : static void
    1677             : snap_close( fd_snapsv_t * ctx,
    1678             :             ulong         slot,
    1679           0 :             ulong         base_slot ) {
    1680           0 :   snap_entry_t * entry = snap_map_update( ctx->snap_map, &(snap_key_t){slot, base_slot} );
    1681           0 :   if( FD_UNLIKELY( !entry ) ) return; /* ignore */
    1682             : 
    1683           0 :   if( entry->locked ) {
    1684           0 :     struct flock lock = {
    1685           0 :       .l_type   = F_UNLCK,
    1686           0 :       .l_whence = SEEK_SET
    1687           0 :     };
    1688           0 :     if( FD_UNLIKELY( fcntl( entry->fd, F_SETLK, &lock ) ) ) {
    1689           0 :       FD_LOG_ERR(( "fcntl(F_UNLCK, %lu, %lu) failed (%i-%s)", slot, base_slot, errno, fd_io_strerror( errno ) ));
    1690           0 :     }
    1691           0 :     entry->locked = 0;
    1692           0 :   }
    1693           0 :   entry->fd = -1;
    1694             : 
    1695           0 :   snap_map_remove( ctx->snap_map, entry );
    1696           0 :   ctx->newest_full = ctx->newest_incr = NULL;
    1697           0 :   if( base_slot==ULONG_MAX ) {
    1698           0 :     FD_CHECK_CRIT( ctx->snap_cnt_full, "full snapshot count underflow" );
    1699           0 :     ctx->snap_cnt_full--;
    1700           0 :   } else {
    1701           0 :     FD_CHECK_CRIT( ctx->snap_cnt_incr, "incremental snapshot count underflow" );
    1702           0 :     ctx->snap_cnt_incr--;
    1703           0 :   }
    1704           0 : }
    1705             : 
    1706             : /* msg_snapmk is called for every snapmk_out frag. */
    1707             : 
    1708             : static void
    1709             : msg_snapmk( fd_snapsv_t *           ctx,
    1710             :             ulong                   msg_type, /* sig */
    1711             :             fd_snapmk_msg_t const * msg,
    1712           0 :             ulong                   msg_sz ) {
    1713           0 :   switch( msg_type ) {
    1714           0 :   case FD_SNAPMK_MSG_FOUND: {
    1715           0 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_found_t), "ABI mismatch" );
    1716           0 :     snap_open( ctx, msg->found.slot, msg->found.base_slot, msg->found.pool_idx, msg->found.sz, msg->found.name );
    1717           0 :     break;
    1718           0 :   }
    1719           0 :   case FD_SNAPMK_MSG_CREATED: {
    1720           0 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_created_t), "ABI mismatch" );
    1721           0 :     snap_open( ctx, msg->created.slot, msg->created.base_slot, msg->created.pool_idx, msg->created.sz, msg->created.name );
    1722           0 :     break;
    1723           0 :   }
    1724           0 :   case FD_SNAPMK_MSG_DELETED: {
    1725           0 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_deleted_t), "ABI mismatch" );
    1726           0 :     snap_close( ctx, msg->deleted.slot, msg->deleted.base_slot );
    1727           0 :     break;
    1728           0 :   }
    1729           0 :   default:
    1730           0 :     break;
    1731           0 :   }
    1732           0 : }
    1733             : 
    1734             : /* returnable_frag is called for every input frag */
    1735             : 
    1736             : static int
    1737             : returnable_frag( fd_snapsv_t *       ctx,
    1738             :                  ulong               in_idx,
    1739             :                  ulong               seq,
    1740             :                  ulong               sig,
    1741             :                  ulong               chunk,
    1742             :                  ulong               sz,
    1743             :                  ulong               ctl,
    1744             :                  ulong               tsorig,
    1745             :                  ulong               tspub,
    1746           0 :                  fd_stem_context_t * stem ) {
    1747           0 :   (void)ctl; (void)tsorig; (void)tspub; (void)stem;
    1748           0 :   ctx->in[ in_idx ].seq_cons = seq;
    1749           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1750           0 :   case IN_KIND_SNAPMK:
    1751           0 :     FD_CHECK_CRIT( chunk >= ctx->in[ in_idx ].chunk0 &&
    1752           0 :                    chunk <= ctx->in[ in_idx ].wmark &&
    1753           0 :                    sz    <= ctx->in[ in_idx ].mtu,
    1754           0 :                    "input frag is out-of-bounds" );
    1755           0 :     fd_snapmk_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    1756           0 :     msg_snapmk( ctx, sig, msg, sz );
    1757           0 :     return 0; /* ok */
    1758           0 :   default:
    1759           0 :     FD_LOG_CRIT(( "unhandled frag from in_idx=%lu", in_idx ));
    1760           0 :   }
    1761           0 : }
    1762             : 
    1763             : static void
    1764           0 : metrics_write( fd_snapsv_t * ctx ) {
    1765           0 :   FD_MGAUGE_SET( SNAPSV, SNAPSHOTS_AVAILABLE_FULL,        ctx->snap_cnt_full );
    1766           0 :   FD_MGAUGE_SET( SNAPSV, SNAPSHOTS_AVAILABLE_INCREMENTAL, ctx->snap_cnt_incr );
    1767           0 :   FD_MGAUGE_SET( SNAPSV, CONN_ACTIVE,                     ctx->conn_cnt      );
    1768           0 : }
    1769             : 
    1770           0 : #define STEM_BURST 1UL
    1771           0 : #define STEM_CALLBACK_CONTEXT_TYPE    fd_snapsv_t
    1772           0 : #define STEM_CALLBACK_CONTEXT_ALIGN   alignof(fd_snapsv_t)
    1773           0 : #define STEM_CALLBACK_AFTER_CREDIT    after_credit
    1774           0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
    1775           0 : #define STEM_CALLBACK_METRICS_WRITE   metrics_write
    1776             : #include "../../disco/stem/fd_stem.c"
    1777             : 
    1778             : #ifndef FD_TILE_TEST
    1779             : fd_topo_run_tile_t fd_tile_snapsv = {
    1780             :   .name                     = "snapsv",
    1781             :   .rlimit_file_cnt_fn       = rlimit_file_cnt,
    1782             :   .populate_allowed_fds     = populate_allowed_fds,
    1783             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1784             :   .scratch_align            = scratch_align,
    1785             :   .scratch_footprint        = scratch_footprint,
    1786             :   .privileged_init          = privileged_init,
    1787             :   .unprivileged_init        = unprivileged_init,
    1788             :   .run                      = stem_run,
    1789             :   .rlimit_nproc             = 1024UL,
    1790             : };
    1791             : #endif

Generated by: LCOV version 1.14