LCOV - code coverage report
Current view: top level - discof/backup - fd_snapsv_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 1115 1460 76.4 %
Date: 2026-11-12 04:25:03 Functions: 63 186 33.9 %

          Line data    Source code
       1             : /* The snapsv tile is a HTTP file server that serves local snapshots to
       2             :    remote peers.
       3             : 
       4             :    snapsv is entirely single-threaded and uses tango queues to communicate
       5             :    with other parts of the system (e.g. snapmk, the snapshot producer, and
       6             :    event feeds for the GUI server and telemetry client).  snapsv scales by
       7             :    launching multiple independent tile instances using SO_REUSEPORT /
       8             :    receive-side scaling.
       9             : 
      10             :    This tile uses io_uring for network and file I/O.
      11             : 
      12             :    snapsv is fully cooperatively scheduled using io_uring waits.
      13             :    It wakes on network events, timeouts, file I/O completions, and
      14             :    futex wakes (new tango messages).
      15             : 
      16             :    Scheduling is event-driven (lazy).  Apart from the initial accept op,
      17             :    every other op is triggered by a completion. */
      18             : 
      19             : #define _GNU_SOURCE
      20             : #include "fd_snapsv_tile.h"
      21             : #include "fd_snapmk_tile.h"
      22             : #include "../../disco/fd_clock_tile.h"
      23             : #include "../../disco/stem/fd_stem.h"
      24             : #include "../../disco/topo/fd_topo.h"
      25             : #include "../../disco/metrics/fd_metrics.h"
      26             : #include "../../util/net/fd_ip6.h"
      27             : #include <errno.h>
      28             : #include <fcntl.h>
      29             : #include <netinet/in.h>
      30             : #include <sys/socket.h>
      31             : #include <sys/types.h>
      32             : #include <linux/futex.h>
      33             : #include "../../util/io_uring/fd_io_uring.h"
      34             : #include "../../util/io_uring/fd_io_uring_setup.h"
      35             : #include "../../util/io_uring/fd_io_uring_register.h"
      36             : #include "../../third_party/picohttpparser/picohttpparser.h"
      37             : #include "generated/fd_snapsv_tile_seccomp.h"
      38             : 
      39             : /* Open addressed hash map of snapshots */
      40             : 
      41             : struct snap_key {
      42             :   ulong slot;
      43             :   ulong base_slot; /* ULONG_MAX if full snap */
      44             : };
      45             : typedef struct snap_key snap_key_t;
      46             : 
      47             : struct snap_entry {
      48             :   snap_key_t key;
      49             :   uchar hash[ 32 ];
      50             :   ulong sz;
      51             :   int   fd;
      52             :   uint  locked:1;
      53             :   uint  is_zstd:1;
      54             : };
      55             : typedef struct snap_entry snap_entry_t;
      56             : 
      57             : static ulong
      58             : snap_key_hash( snap_key_t const * key,
      59     3170823 :                ulong              seed ) {
      60             :   /* xxHash3 adapted */
      61     3170823 :   ulong k0 = key->base_slot;
      62     3170823 :   ulong k1 = key->slot;
      63             : 
      64     3170823 :   ulong lo = k0 ^ (0x6782737bea4239b9UL + seed);
      65     3170823 :   ulong hi = k1 ^ (0xaf56bc3b0996523aUL - seed);
      66             : 
      67     3170823 :   uint128 product = (uint128)lo * (uint128)hi;
      68     3170823 :   ulong   fold    = (ulong)product ^ (ulong)( product>>64 );
      69             : 
      70     3170823 :   ulong acc = 16UL + fd_ulong_bswap( lo ) + hi + fold;
      71     3170823 :   acc ^= acc >> 37;
      72     3170823 :   acc *= 0x165667919E3779F9UL;
      73     3170823 :   acc ^= acc >> 32;
      74     3170823 :   return acc;
      75     3170823 : }
      76             : 
      77             : #define MAP_NAME            snap_map
      78     7767507 : #define MAP_ELE_T           snap_entry_t
      79             : #define MAP_KEY_T           snap_key_t
      80     3453489 : #define MAP_KEY_EQ(k0,k1)   (((k0)->slot==(k1)->slot) & ((k0)->base_slot==(k1)->base_slot))
      81     3170760 : #define MAP_KEY_HASH(k,s)   snap_key_hash( (k), (s) )
      82     6760605 : #define MAP_ELE_IS_FREE(e)  (((e)->key.slot==ULONG_MAX) & ((e)->key.base_slot==ULONG_MAX))
      83       29823 : #define MAP_ELE_FREE(c,e)   ((e)->key=(snap_key_t){ULONG_MAX,ULONG_MAX})
      84        9975 : #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)
      85             : #include "../../util/tmpl/fd_map_slot.c"
      86             : 
      87             : /* Tile state */
      88             : 
      89             : #define IN_LINK_MAX 1 /* cannot trivially grow */
      90             : 
      91             : /* 8 ought to be enough ... The SQE/CQE count per conn is limited due
      92             :    to fixed depth pipelining. */
      93         486 : #define SQE_PER_CONN (8UL)
      94         486 : #define CQE_PER_CONN (8UL)
      95             : 
      96             : /* io_uring fixed file indices */
      97             : 
      98         324 : #define FIXED_FD_LISTEN (0U) /* TCP listen socket */
      99         732 : #define FIXED_FD_CNT    (1U)
     100             : 
     101             : /* HTTP request head limits.  The request head is accumulated in the
     102             :    conn's iobuf, therefore it can never exceed the iobuf size. */
     103             : 
     104         183 : #define REQ_HEADER_MAX (64UL)  /* max number of request headers */
     105             : 
     106             : /* RES_HDR_MAX bounds the number of bytes that any of the
     107             :    build_{err,snap,redirect}_hdr functions write into the response
     108             :    buffer, including the NUL terminator. */
     109             : 
     110             : #define RES_HDR_MAX (256UL)
     111             : 
     112             : /* conn state */
     113             : 
     114        5259 : #define CONN_STATE_FREE          (0U) /* conn slot is unused */
     115         324 : #define CONN_STATE_ALLOCATED     (1U)
     116        6660 : #define CONN_STATE_ACCEPT        (2U) /* waiting for HTTP conn to be accepted */
     117      936798 : #define CONN_STATE_REQ_PEEK      (3U) /* peeking request bytes */
     118      936216 : #define CONN_STATE_REQ_SKIP      (4U) /* consume in-flight, head still incomplete */
     119      468057 : #define CONN_STATE_REQ_DONE      (5U) /* consume in-flight, head complete */
     120         180 : #define CONN_STATE_RES_WRITE_ERR (6U) /* writing response header */
     121         156 : #define CONN_STATE_RES_WRITE_HDR (7U) /* writing response header */
     122         117 : #define CONN_STATE_RES_REDIRECT  (8U) /* writing snap redirect */
     123          36 : #define CONN_STATE_RES_SHOVEL    (9U) /* pipelinined snap data RX & TX */
     124             : 
     125             : struct snapsv_conn {
     126             :   uint state;     /* CONN_STATE_{...} */
     127             :   uint iobuf_idx; /* iobuf for networking, UINT_MAX if none */
     128             :   uint rdbuf_idx; /* iobuf for disk read, UINT_MAX if none */
     129             :   uint rdbuf_len; /* completed bytes in rdbuf */
     130             : 
     131             :   uint sick:1;          /* force close after current request? */
     132             :   uint disk_inflight:1; /* file read pending completion */
     133             :   uint net_inflight:1;  /* tcp send pending completion */
     134             :   uint closing:1;       /* close once shovel operations drain? */
     135             :   uint shutdown:1;      /* idle sweep issued a socket shutdown? */
     136             : 
     137             :   struct {
     138             :     ulong req_seq;  /* seq no of this HTTP request */
     139             :     uint  len;
     140             :     uint  skip_len;      /* bytes requested by the in-flight CONSUME_FRAG */
     141             :     uint  head:1;        /* HEAD request? */
     142             :     uint  get_snap:1;    /* GET request for a snapshot? (streaming) */
     143             :     uint  incremental:1; /* incremental snap requested? */
     144             :   } req;
     145             : 
     146             :   struct {
     147             :     uint  sent;
     148             :     uint  len;
     149             :     uint  status;     /* http status code */
     150             :     long  hdr_ts;     /* unix nanos timestamp when we started sending resp headers */
     151             :     uchar close_kind; /* FD_SNAPSV_CLOSE_* */
     152             :   } res;
     153             : 
     154             :   struct {
     155             :     snap_key_t key;
     156             :     ulong file_sz; /* snapshot file size */
     157             :     ulong req_off0; /* file offset of request */
     158             :     ulong req_off1; /* [req_off,req_off1) give the requested file range */
     159             :     ulong req_cur;  /* next file offset to read */
     160             :     ulong req_sent; /* payload bytes handed to the socket */
     161             :     uint  range:1;  /* range request? */
     162             :   } snap;
     163             : 
     164             :   fd_kernel_timespec_t idle_timeout;
     165             : 
     166             :   /* active_ts: last time this conn did work worth keeping it alive for:
     167             :      accept, a completed request, or snapshot payload delivery.  In unix
     168             :      nanoseconds. */
     169             :   long active_ts;
     170             : 
     171             :   fd_ip6_addr_t peer_ip;
     172             :   ushort        peer_port;
     173             : };
     174             : 
     175             : typedef struct snapsv_conn snapsv_conn_t;
     176             : 
     177             : struct fd_snapsv {
     178             : 
     179             :   uint kind_id;
     180             : 
     181             :   fd_clock_tile_t clock[1];
     182             : 
     183             :   /* io_uring */
     184             :   fd_io_uring_t ring[1];
     185             :   void *        ring_shmem;
     186             : 
     187             :   /* I/O buffers */
     188             :   uchar * iobuf0;
     189             :   uint *  iobuf_free;
     190             :   uint    iobuf_free_cnt;
     191             :   uint    iobuf_sz;
     192             : 
     193             :   /* TCP sockets */
     194             :   int             listen_fd;
     195             :   uint            conn0_fd_idx; /* io_uring file index */
     196             :   ulong           conn_cnt;
     197             :   ulong           conn_max;
     198             :   snapsv_conn_t * conn0;
     199             :   uint *          conn_free;
     200             :   uint            conn_free_cnt;
     201             :   long            idle_timeout_nanos;
     202             :   fd_kernel_timespec_t send_timeout;
     203             : 
     204             :   /* Conn accept */
     205             :   uint                    accept_inflight:1;
     206             :   struct sockaddr_storage accept_addr;
     207             :   socklen_t               accept_addr_len;
     208             : 
     209             :   /* snapshot hashmap */
     210             :   snap_map_t snap_map[1];
     211             :   ulong      snap_cnt_full;
     212             :   ulong      snap_cnt_incr;
     213             :   ulong      snap_max;
     214             : 
     215             :   /* cache: newest snaps */
     216             :   snap_entry_t * newest_full;
     217             :   snap_entry_t * newest_incr;
     218             : 
     219             :   /* snapshot files */
     220             :   int dir_fd;
     221             : 
     222             :   /* tango input links */
     223             :   uchar in_kind[ IN_LINK_MAX ];
     224             :   struct {
     225             :     void * mem;
     226             :     ulong  chunk0;
     227             :     ulong  wmark;
     228             :     ulong  mtu;
     229             : 
     230             :     fd_frag_meta_t const * mcache;
     231             :     ulong const *          seq_prod; /* mcache->seq[0] */
     232             :     ulong                  seq_cons; /* last consumed seq */
     233             :     uint                   depth;
     234             :     uint                   futex_armed:1;
     235             :   } in[ IN_LINK_MAX ];
     236             : 
     237             :   /* streaming access logs */
     238             :   ulong req_seq;
     239             :   struct {
     240             :     long        out_idx; /* -1 if the link is absent */
     241             :     fd_wksp_t * mem;
     242             :     ulong       chunk0;
     243             :     ulong       wmark;
     244             :     ulong       chunk;
     245             :     long        next_heartbeat; /* nanos */
     246             :   } out;
     247             : 
     248             : };
     249             : 
     250             : typedef struct fd_snapsv fd_snapsv_t;
     251             : 
     252             : /* snapsv_udata_t represents io_uring user data. */
     253             : 
     254             : union snapsv_udata {
     255             :   ulong user_data;
     256             : 
     257             :   struct __attribute__((packed)) {
     258             :     uint  conn_idx;
     259             :     uchar op;
     260         486 : #define UDATA_OP_ACCEPT       ((uchar)0) /* accept new conn */
     261         444 : #define UDATA_OP_PEEK         ((uchar)1) /* read request bytes */
     262          36 : #define UDATA_OP_CONSUME_FRAG ((uchar)2) /* consume request frag */
     263         330 : #define UDATA_OP_CONSUME_TAIL ((uchar)3) /* consume complete request */
     264         243 : #define UDATA_OP_WRITE_HDR    ((uchar)4) /* write response header */
     265         492 : #define UDATA_OP_SHOVEL_DISK  ((uchar)5) /* read snapshot data */
     266         474 : #define UDATA_OP_SHOVEL_NET   ((uchar)6) /* send snapshot data */
     267       29241 : #define UDATA_OP_FUTEX        ((uchar)7) /* wait for tango frag */
     268        1161 : #define UDATA_OP_TIMEOUT      ((uchar)8) /* linked socket op timeout */
     269         555 : #define UDATA_OP_SHUTDOWN     ((uchar)9) /* idle sweep socket shutdown */
     270             :   };
     271             : };
     272             : 
     273             : typedef union snapsv_udata snapsv_udata_t;
     274             : 
     275             : FD_STATIC_ASSERT( sizeof(snapsv_udata_t)==sizeof(ulong), layout );
     276             : 
     277         288 : #define IN_KIND_SNAPMK 0
     278             : 
     279             : static ulong
     280         486 : scratch_align( void ) {
     281         486 :   return FD_SHMEM_NORMAL_PAGE_SZ;
     282         486 : }
     283             : 
     284             : FD_FN_PURE static ulong
     285         324 : snapsv_fd_max( fd_topo_tile_t const * tile ) {
     286         324 :   return FIXED_FD_CNT + tile->snapsv.snap_max + tile->snapsv.conn_max;
     287         324 : }
     288             : 
     289             : FD_FN_PURE static ulong
     290         486 : snapsv_map_ele_max( fd_topo_tile_t const * tile ) {
     291         486 :   return fd_ulong_pow2_up( tile->snapsv.snap_max );
     292         486 : }
     293             : 
     294             : FD_FN_PURE static ulong
     295         486 : snapsv_sq_depth( fd_topo_tile_t const * tile ) {
     296         486 :   return fd_ulong_pow2_up( SQE_PER_CONN*tile->snapsv.conn_max );
     297         486 : }
     298             : 
     299             : FD_FN_PURE static ulong
     300         486 : snapsv_cq_depth( fd_topo_tile_t const * tile ) {
     301         486 :   return fd_ulong_pow2_up( CQE_PER_CONN*tile->snapsv.conn_max );
     302         486 : }
     303             : 
     304             : static ulong
     305         162 : scratch_footprint( fd_topo_tile_t const * tile ) {
     306         162 :   ulong sq_depth = snapsv_sq_depth( tile );
     307         162 :   ulong cq_depth = snapsv_cq_depth( tile );
     308         162 :   ulong l = FD_LAYOUT_INIT;
     309         162 :   l = FD_LAYOUT_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     310         162 :   l = FD_LAYOUT_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     311         162 :   l = FD_LAYOUT_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     312         162 :   l = FD_LAYOUT_APPEND( l, alignof(int),              snapsv_fd_max( tile )*sizeof(int) );
     313         162 :   l = FD_LAYOUT_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     314         162 :   l = FD_LAYOUT_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     315         162 :   l = FD_LAYOUT_APPEND( l, alignof(uint),             tile->snapsv.conn_max * 2 * sizeof(uint) );
     316         162 :   l = FD_LAYOUT_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max * (tile->snapsv.send_buffer_size_kib<<11) );
     317         162 :   return FD_LAYOUT_FINI( l, scratch_align() );
     318         162 : }
     319             : 
     320             : /* snapsv_listen creates the TCP listen socket */
     321             : 
     322             : static int
     323             : snapsv_listen( fd_ip6_addr_t const * listen_addr,
     324             :                ushort                listen_port,
     325           0 :                ulong                 conn_max ) {
     326           0 :   int ip4 = fd_ip6_addr_is_ip4_mapped( listen_addr->addr ) && !listen_addr->scope_id;
     327             : 
     328           0 :   int sock_fd = socket( ip4 ? AF_INET : AF_INET6, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     329           0 :   if( FD_UNLIKELY( -1==sock_fd ) ) {
     330           0 :     FD_LOG_ERR(( "socket(AF_INET%s,SOCK_STREAM) failed (%i-%s)", ip4 ? "" : "6", errno, fd_io_strerror( errno ) ));
     331           0 :   }
     332             : 
     333             :   /* recover quickly from restart, if there are lingering TIME_WAIT conns */
     334           0 :   int optval = 1;
     335           0 :   if( FD_UNLIKELY( -1==setsockopt( sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int) ) ) ) {
     336           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_REUSEADDR) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     337           0 :   }
     338             :   /* load balancing across multiple snapsv tiles */
     339           0 :   optval = 1;
     340           0 :   if( FD_UNLIKELY( -1==setsockopt( sock_fd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(int) ) ) ) {
     341           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_REUSEPORT) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     342           0 :   }
     343             :   /* TCP urgent meaningless for snapshot protocol */
     344           0 :   optval = 1;
     345           0 :   if( FD_UNLIKELY( -1==setsockopt( sock_fd, SOL_SOCKET, SO_OOBINLINE, &optval, sizeof(int) ) ) ) {
     346           0 :     FD_LOG_ERR(( "setsockopt(SOL_SOCKET,SO_OOBINLINE) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     347           0 :   }
     348             : 
     349           0 :   union {
     350           0 :     struct sockaddr_in  ip4;
     351           0 :     struct sockaddr_in6 ip6;
     352           0 :   } addr = {0};
     353           0 :   ulong addr_sz;
     354             : 
     355           0 :   if( ip4 ) {
     356           0 :     addr.ip4 = (struct sockaddr_in){
     357           0 :       .sin_family      = AF_INET,
     358           0 :       .sin_port        = fd_ushort_bswap( listen_port ),
     359           0 :       .sin_addr.s_addr = fd_ip6_addr_to_ip4( listen_addr->addr )
     360           0 :     };
     361           0 :     addr_sz = sizeof(struct sockaddr_in);
     362           0 :   } else {
     363           0 :     int v6only = 0;
     364           0 :     if( FD_UNLIKELY( -1==setsockopt( sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(int) ) ) ) {
     365           0 :       FD_LOG_ERR(( "setsockopt(IPPROTO_IPV6,IPV6_V6ONLY) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     366           0 :     }
     367           0 :     addr.ip6 = (struct sockaddr_in6){
     368           0 :       .sin6_family   = AF_INET6,
     369           0 :       .sin6_port     = fd_ushort_bswap( listen_port ),
     370           0 :       .sin6_scope_id = listen_addr->scope_id
     371           0 :     };
     372           0 :     memcpy( addr.ip6.sin6_addr.s6_addr, listen_addr->addr, 16UL );
     373           0 :     addr_sz = sizeof(struct sockaddr_in6);
     374           0 :   }
     375             : 
     376           0 :   char addr_cstr[ FD_IP6_ADDR_CSTR_MAX ];
     377           0 :   if( FD_UNLIKELY( -1==bind( sock_fd, fd_type_pun( &addr ), (uint)addr_sz ) ) ) {
     378           0 :     FD_LOG_ERR(( "bind(%s:%u) failed (%i-%s)",
     379           0 :                  fd_ip6_addr_cstr( addr_cstr, listen_addr ), listen_port,
     380           0 :                  errno, fd_io_strerror( errno ) ));
     381           0 :   }
     382           0 :   if( FD_UNLIKELY( -1==listen( sock_fd, (int)conn_max ) ) ) {
     383           0 :     FD_LOG_ERR(( "listen() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     384           0 :   }
     385             : 
     386           0 :   FD_LOG_NOTICE(( "snapshot server listening at %shttp://%s:%u%s",
     387           0 :                   fd_log_style_bold(), fd_ip6_addr_cstr( addr_cstr, listen_addr ), listen_port,
     388           0 :                   fd_log_style_normal() ));
     389             : 
     390           0 :   return sock_fd;
     391           0 : }
     392             : 
     393             : static void
     394             : privileged_init( fd_topo_t const *      topo,
     395           0 :                  fd_topo_tile_t const * tile ) {
     396           0 :   ulong fd_max   = snapsv_fd_max( tile );
     397           0 :   ulong sq_depth = snapsv_sq_depth( tile );
     398           0 :   ulong cq_depth = snapsv_cq_depth( tile );
     399           0 :   FD_SCRATCH_ALLOC_INIT( l, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
     400           0 :   fd_snapsv_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     401           0 :   void *        ring_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     402           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     403           0 :   int *         fd_table = FD_SCRATCH_ALLOC_APPEND( l, alignof(int),              fd_max*sizeof(int) );
     404           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     405           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     406           0 :   /*               */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*2UL*sizeof(uint) );
     407           0 :   uchar *       iobuf0   = FD_SCRATCH_ALLOC_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max*(tile->snapsv.send_buffer_size_kib<<11) );
     408             : 
     409           0 :   memset( ctx, 0, sizeof(fd_snapsv_t) );
     410           0 :   memset( fd_table, 0xff, fd_max*sizeof(int) );
     411             : 
     412           0 :   FD_CHECK_ERR( tile->snapsv.conn_max, "snapsv conn_max is zero" );
     413           0 :   FD_CHECK_ERR( tile->snapsv.snap_max, "snapsv snap_max is zero" );
     414           0 :   ctx->snap_max = tile->snapsv.snap_max;
     415             : 
     416             :   /* TCP listen socket */
     417             : 
     418           0 :   ctx->conn_max  = tile->snapsv.conn_max;
     419           0 :   ctx->listen_fd = snapsv_listen( &tile->snapsv.listen_addr, tile->snapsv.listen_port, tile->snapsv.conn_max );
     420             : 
     421             :   /* io_uring setup */
     422             : 
     423           0 :   ctx->ring_shmem = ring_mem;
     424           0 :   fd_io_uring_params_t params[1];
     425           0 :   fd_io_uring_params_init( params, (uint)sq_depth );
     426           0 :   params->flags |= FD_IORING_SETUP_COOP_TASKRUN;
     427             : 
     428           0 :   if( FD_UNLIKELY( !fd_io_uring_init_shmem( ctx->ring, params, ctx->ring_shmem, sq_depth, cq_depth ) ) ) {
     429           0 :     FD_LOG_ERR(( "fd_io_uring_init_shmem failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     430           0 :   }
     431             : 
     432             :   /* registered buffer to help with DMA */
     433             : 
     434           0 :   struct iovec iovec = {
     435           0 :     .iov_base = iobuf0,
     436           0 :     .iov_len  = tile->snapsv.conn_max*(tile->snapsv.send_buffer_size_kib<<11)
     437           0 :   };
     438           0 :   if( FD_UNLIKELY( fd_io_uring_register_buffers( ctx->ring->ioring_fd, &iovec, 1UL )<0 ) ) {
     439           0 :     FD_LOG_ERR(( "io_uring_register_buffers() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     440           0 :   }
     441             : 
     442             :   /* Build file descriptor table */
     443           0 :   FD_TEST( tile->snapsv.snap_max<=FD_SNAP_MAX );
     444           0 :   fd_table[ FIXED_FD_LISTEN ] = ctx->listen_fd;
     445           0 :   uint fixed_fd = FIXED_FD_CNT;
     446           0 :   for( ulong i=0UL; i<tile->snapsv.snap_max; i++ ) {
     447           0 :     fd_table[ fixed_fd++ ] = FD_SNAP_RO_FD( i );
     448           0 :   }
     449           0 :   ctx->conn0_fd_idx = fixed_fd;
     450           0 :   if( FD_UNLIKELY( fd_io_uring_register_files( ctx->ring->ioring_fd, fd_table, fd_max )<0 ) ) {
     451           0 :     FD_LOG_ERR(( "io_uring_register_files() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     452           0 :   }
     453             : 
     454           0 :   uint max_workers[2] = { tile->snapsv.io_worker_cnt, tile->snapsv.io_worker_cnt };
     455           0 :   if( FD_UNLIKELY( fd_io_uring_register( ctx->ring->ioring_fd, FD_IORING_REGISTER_IOWQ_MAX_WORKERS, max_workers, 2U )<0 ) ) {
     456           0 :     FD_LOG_ERR(( "io_uring_register(IORING_REGISTER_IOWQ_MAX_WORKERS) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     457           0 :   }
     458             : 
     459           0 :   fd_io_uring_restriction_t restrictions[] = {
     460           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_ACCEPT       },
     461           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_RECV         },
     462           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_SEND         },
     463           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_READ_FIXED   },
     464           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_FUTEX_WAIT   },
     465           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_LINK_TIMEOUT },
     466           0 :     { .opcode = FD_IORING_RESTRICTION_SQE_OP, .sqe_op = FD_IORING_OP_SHUTDOWN     },
     467           0 :     { .opcode      = FD_IORING_RESTRICTION_REGISTER_OP, /* deregister files */
     468           0 :       .register_op = FD_IORING_REGISTER_FILES_UPDATE },
     469           0 :     { .opcode    = FD_IORING_RESTRICTION_SQE_FLAGS_ALLOWED,
     470           0 :       .sqe_flags = FD_IOSQE_IO_LINK },
     471           0 :     { .opcode    = FD_IORING_RESTRICTION_SQE_FLAGS_REQUIRED,
     472           0 :       .sqe_flags = FD_IOSQE_FIXED_FILE },
     473           0 :   };
     474           0 :   if( FD_UNLIKELY( fd_io_uring_register_restrictions(
     475           0 :         ctx->ring->ioring_fd, restrictions,
     476           0 :         (uint)( sizeof(restrictions)/sizeof(restrictions[0]) ) )<0 ) ) {
     477           0 :     FD_LOG_ERR(( "io_uring_register_restrictions failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     478           0 :   }
     479             : 
     480           0 :   if( FD_UNLIKELY( fd_io_uring_enable_rings( ctx->ring->ioring_fd )<0 ) ) {
     481           0 :     FD_LOG_ERR(( "io_uring_enable_rings failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     482           0 :   }
     483             : 
     484           0 :   FD_TEST( fd_rng_secure( &ctx->snap_map->seed, sizeof(ulong) ) );
     485           0 : }
     486             : 
     487             : static void
     488             : prep_accept( fd_snapsv_t * ctx );
     489             : 
     490             : static void
     491             : unprivileged_init( fd_topo_t const *      topo,
     492         162 :                    fd_topo_tile_t const * tile ) {
     493         162 :   ulong sq_depth = snapsv_sq_depth( tile );
     494         162 :   ulong cq_depth = snapsv_cq_depth( tile );
     495         162 :   FD_SCRATCH_ALLOC_INIT( l, fd_topo_obj_laddr( topo, tile->tile_obj_id ) );
     496         162 :   fd_snapsv_t *   ctx        = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapsv_t),      sizeof(fd_snapsv_t) );
     497         162 :   /*                   */(void)FD_SCRATCH_ALLOC_APPEND( l, fd_io_uring_shmem_align(), fd_io_uring_shmem_footprint( sq_depth, cq_depth ) );
     498         162 :   void *          map_mem    = FD_SCRATCH_ALLOC_APPEND( l, snap_map_align(),          snap_map_footprint( snapsv_map_ele_max( tile ) ) );
     499         162 :   /*                   */(void)FD_SCRATCH_ALLOC_APPEND( l, alignof(int),              snapsv_fd_max( tile )*sizeof(int) );
     500         162 :   snapsv_conn_t * conn0      = FD_SCRATCH_ALLOC_APPEND( l, alignof(snapsv_conn_t),    tile->snapsv.conn_max*sizeof(snapsv_conn_t) );
     501         162 :   uint *          conn_free  = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max*sizeof(uint) );
     502         162 :   uint *          iobuf_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint),             tile->snapsv.conn_max * 2 * sizeof(uint) );
     503         162 :   uchar *         iobuf0     = FD_SCRATCH_ALLOC_APPEND( l, FD_SHMEM_NORMAL_PAGE_SZ,   tile->snapsv.conn_max * (tile->snapsv.send_buffer_size_kib<<11) );
     504             : 
     505         162 :   ctx->kind_id = (uint)tile->kind_id;
     506             : 
     507         162 :   fd_clock_tile_init( ctx->clock );
     508             : 
     509             :   /* snapshot map setup */
     510             : 
     511         162 :   ulong map_ele_max = snapsv_map_ele_max( tile );
     512         162 :   snap_entry_t * slots = snap_map_new( map_mem, map_ele_max, 0 );
     513         162 :   FD_TEST( slots );
     514        5346 :   for( ulong i=0UL; i<map_ele_max; i++ ) {
     515        5184 :     slots[ i ].key = (snap_key_t){ ULONG_MAX,ULONG_MAX };
     516        5184 :   }
     517         162 :   ulong map_seed = ctx->snap_map->seed;
     518         162 :   FD_TEST( snap_map_join( ctx->snap_map, slots, map_ele_max, map_ele_max, map_seed ) );
     519         162 :   ctx->snap_max = tile->snapsv.snap_max;
     520             : 
     521             :   /* link setup */
     522             : 
     523         162 :   FD_CHECK_ERR( tile->in_cnt<=IN_LINK_MAX, "too many input links" );
     524         162 :   FD_CHECK_ERR( tile->in_cnt==1, "snapsv is hardcoded to only support one input link" );
     525         324 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
     526         162 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
     527         162 :     FD_CHECK_ERR( !strcmp( link->name, "snapmk_out" ), "unexpected input link" );
     528         162 :     FD_CHECK_ERR( tile->in_link_poll[ i ], "expecting polled input link" );
     529         162 :     ctx->in_kind[ i ]   = IN_KIND_SNAPMK;
     530         162 :     ctx->in[ i ].mem    = fd_wksp_containing( link->dcache );
     531         162 :     ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
     532         162 :     ctx->in[ i ].wmark  = fd_dcache_compact_wmark( ctx->in[ i ].mem, link->dcache, link->mtu );
     533         162 :     ctx->in[ i ].mtu    = link->mtu;
     534             : 
     535         162 :     ulong depth = fd_mcache_depth( link->mcache );
     536         162 :     FD_CHECK_ERR( depth<=UINT_MAX, "input link mcache too deep" );
     537         162 :     ctx->in[ i ].mcache      = link->mcache;
     538         162 :     ctx->in[ i ].depth       = (uint)depth;
     539         162 :     ctx->in[ i ].seq_prod    = fd_mcache_seq_laddr_const( link->mcache );
     540         162 :     ctx->in[ i ].seq_cons    = ULONG_MAX;
     541         162 :     ctx->in[ i ].futex_armed = 0;
     542         162 :   }
     543             : 
     544         162 :   FD_CHECK_ERR( tile->out_cnt<=1UL, "too many output links" );
     545         162 :   memset( &ctx->out, 0, sizeof(ctx->out) );
     546         162 :   ctx->out.out_idx = -1L;
     547         324 :   for( ulong i=0UL; i<tile->out_cnt; i++ ) {
     548         162 :     fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ];
     549         162 :     FD_CHECK_ERR( !strcmp( link->name, "snapsv_out" ), "unexpected output link" );
     550         162 :     FD_CHECK_ERR( link->mtu>=sizeof(fd_snapsv_msg_t), "snapsv_out link mtu too small" );
     551         162 :     ctx->out.out_idx = (long)i;
     552         162 :     ctx->out.mem     = fd_wksp_containing( link->dcache );
     553         162 :     ctx->out.chunk0  = fd_dcache_compact_chunk0( ctx->out.mem, link->dcache );
     554         162 :     ctx->out.wmark   = fd_dcache_compact_wmark ( ctx->out.mem, link->dcache, link->mtu );
     555         162 :     ctx->out.chunk   = ctx->out.chunk0;
     556         162 :   }
     557         162 :   ctx->out.next_heartbeat = 0L;
     558             : 
     559             :   /* conn table */
     560             : 
     561         162 :   ctx->conn0         = conn0;
     562         162 :   ctx->conn_free     = conn_free;
     563         162 :   ctx->conn_free_cnt = (uint)tile->snapsv.conn_max;
     564         162 :   ctx->idle_timeout_nanos = (long)( tile->snapsv.idle_timeout_millis * ((ulong)1e6) );
     565         162 :   long send_timeout_nanos = (long)( tile->snapsv.send_timeout_millis * ((ulong)1e6) );
     566         162 :   ctx->send_timeout = (fd_kernel_timespec_t) {
     567         162 :     .tv_sec  = send_timeout_nanos / (long)1e9,
     568         162 :     .tv_nsec = send_timeout_nanos % (long)1e9
     569         162 :   };
     570        5346 :   for( ulong i=0UL; i<tile->snapsv.conn_max; i++ ) {
     571        5184 :     conn0[ i ]     = (snapsv_conn_t){ .state = CONN_STATE_FREE, .iobuf_idx = UINT_MAX, .rdbuf_idx = UINT_MAX };
     572        5184 :     conn_free[ i ] = (uint)tile->snapsv.conn_max-1U-(uint)i;
     573        5184 :   }
     574             : 
     575             :   /* io bufs */
     576             : 
     577         162 :   ulong iobuf_sz = tile->snapsv.send_buffer_size_kib<<10;
     578         162 :   FD_CHECK_ERR( iobuf_sz, "send_buffer_size_kib is zero" );
     579         162 :   FD_CHECK_ERR( iobuf_sz>=RES_HDR_MAX, "send_buffer_size_kib is too small" );
     580         162 :   FD_CHECK_ERR( iobuf_sz<=UINT_MAX, "[snapshots.server.send_buffer_size_kib] is too large, must be at most 4194303 KiB" );
     581         162 :   ulong iobuf_cnt = tile->snapsv.conn_max * 2;
     582         162 :   ctx->iobuf_sz       = (uint)iobuf_sz;
     583         162 :   ctx->iobuf0         = iobuf0;
     584         162 :   ctx->iobuf_free     = iobuf_free;
     585         162 :   ctx->iobuf_free_cnt = (uint)iobuf_cnt;
     586       10530 :   for( ulong i=0UL; i<iobuf_cnt; i++ ) {
     587       10368 :     iobuf_free[ i ] = (uint)iobuf_cnt-1U-(uint)i;
     588       10368 :   }
     589             : 
     590             :   /* kick off main async op */
     591             : 
     592         162 :   prep_accept( ctx );
     593         162 : }
     594             : 
     595             : /* iobuf_alloc lends an iobuf to a conn. */
     596             : 
     597             : static void
     598             : iobuf_alloc( fd_snapsv_t * ctx,
     599         633 :              uint *        slot ) {
     600         633 :   if( FD_LIKELY( *slot!=UINT_MAX ) ) return;
     601             :   /* Every conn holds at most two buffers (iobuf and rdbuf).  This
     602             :      slot is empty, so this conn holds at most one; a pool sized for
     603             :      two buffers per conn therefore has a free buffer for this slot. */
     604         630 :   FD_CHECK_CRIT( ctx->iobuf_free_cnt, "iobuf pool exhausted" );
     605         630 :   *slot = ctx->iobuf_free[ --ctx->iobuf_free_cnt ];
     606         630 : }
     607             : 
     608             : /* iobuf_free frees a conn iobuf (if any). */
     609             : 
     610             : static void
     611             : iobuf_free( fd_snapsv_t * ctx,
     612         630 :             uint *        slot ) {
     613         630 :   if( FD_UNLIKELY( *slot==UINT_MAX ) ) return;
     614         543 :   FD_CHECK_CRIT( ctx->iobuf_free_cnt < 2U*(uint)ctx->conn_max, "iobuf double free detected" );
     615         543 :   ctx->iobuf_free[ ctx->iobuf_free_cnt++ ] = *slot;
     616         543 :   *slot = UINT_MAX;
     617         543 : }
     618             : 
     619             : /* conn_alloc pops a new conn from the free stack / object pool.  Returns
     620             :    the index of the allocated conn. */
     621             : 
     622             : static uint
     623         324 : conn_alloc( fd_snapsv_t * ctx ) {
     624         324 :   if( FD_UNLIKELY( !ctx->conn_free_cnt ) ) return UINT_MAX;
     625         324 :   uint            conn_idx = ctx->conn_free[ --ctx->conn_free_cnt ];
     626         324 :   snapsv_conn_t * conn     = &ctx->conn0[ conn_idx ];
     627         324 :   conn->state = CONN_STATE_ALLOCATED;
     628         324 :   FD_CHECK_CRIT( conn->iobuf_idx==UINT_MAX, "memory corruption detected" );
     629         324 :   FD_CHECK_CRIT( conn->rdbuf_idx==UINT_MAX, "memory corruption detected" );
     630         324 :   return conn_idx;
     631         324 : }
     632             : 
     633             : /* conn_free releases all conn resources and returns the conn to the free
     634             :    pool. */
     635             : 
     636             : static void
     637             : conn_free( fd_snapsv_t * ctx,
     638          75 :            uint          conn_idx ) {
     639          75 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     640          75 :   FD_CHECK_CRIT( conn->state!=CONN_STATE_FREE,       "double free detected" );
     641          75 :   FD_CHECK_CRIT( ctx->conn_free_cnt < ctx->conn_max, "double free detected" );
     642          75 :   iobuf_free( ctx, &conn->iobuf_idx );
     643          75 :   iobuf_free( ctx, &conn->rdbuf_idx );
     644          75 :   *conn = (snapsv_conn_t){ .state = CONN_STATE_FREE, .iobuf_idx = UINT_MAX, .rdbuf_idx = UINT_MAX };
     645          75 :   ctx->conn_free[ ctx->conn_free_cnt++ ] = conn_idx;
     646          75 : }
     647             : 
     648             : /* event_prepare prepares a snapsv_out frag (HTTP access log), which the
     649             :    caller can fill in with more detail.  Returns a pointer to an
     650             :    unpublished frag in shm dcache.  There can only be one in-preparation
     651             :    frag at a time.
     652             : 
     653             :    Currently, snapsv_out frags are only generated for "GET /snapshot-*"
     654             :    requests.  This may change in the future. */
     655             : 
     656             : static fd_snapsv_msg_snap_t *
     657             : event_prepare( fd_snapsv_t const * ctx,
     658         336 :                uint                conn_idx ) {
     659         336 :   snapsv_conn_t const * conn = &ctx->conn0[ conn_idx ];
     660         336 :   fd_snapsv_msg_t * msg = fd_chunk_to_laddr( ctx->out.mem, ctx->out.chunk );
     661         336 :   msg->snap = (fd_snapsv_msg_snap_t) {
     662         336 :     .key = (fd_snapsv_conn_key_t) {
     663         336 :       .kind_id  = ctx->kind_id,
     664         336 :       .req_seq  = conn->req.req_seq,
     665         336 :       .slot_idx = conn_idx
     666         336 :     },
     667         336 :     .slot       = conn->snap.key.slot,
     668         336 :     .base_slot  = conn->snap.key.base_slot,
     669         336 :     .snap_sz    = conn->snap.file_sz,
     670         336 :     .req_off    = conn->snap.req_off0,
     671         336 :     .req_sz     = conn->snap.req_off1 - conn->snap.req_off0,
     672         336 :     .req_cur    = conn->snap.req_sent,
     673         336 :     .req_ip     = conn->peer_ip,
     674         336 :     .req_port   = conn->peer_port,
     675         336 :     .resp_ts    = conn->res.hdr_ts,
     676         336 :     .close_kind = conn->res.close_kind
     677         336 :   };
     678         336 :   return &msg->snap;
     679         336 : }
     680             : 
     681             : /* event_publish publishes the in-preparation snapsv_out frag (earlier
     682             :    alloc). */
     683             : 
     684             : static void
     685             : event_publish( fd_snapsv_t *       ctx,
     686             :                fd_stem_context_t * stem,
     687             :                long                now, /* unix nanos */
     688             :                int                 som,
     689         336 :                int                 eom ) {
     690         336 :   ulong sig   = FD_SNAPSV_MSG_SNAP;
     691         336 :   ulong chunk = ctx->out.chunk;
     692         336 :   ulong sz    = sizeof(fd_snapsv_msg_snap_t);
     693         336 :   ulong ctl   = fd_frag_meta_ctl( 0UL, som, eom, 0 );
     694         336 :   ulong tspub = fd_frag_meta_ts_comp( now );
     695         336 :   ctx->out.chunk = fd_dcache_compact_next( chunk, sz, ctx->out.chunk0, ctx->out.wmark );
     696         336 :   fd_stem_publish( stem, (ulong)ctx->out.out_idx, sig, chunk, sz, ctl, 0UL, tspub );
     697         336 : }
     698             : 
     699             : /* event_snap emits an access log event for a snapshot request.  Silently
     700             :    does nothing if the tile was configured without a snapsv_out link. */
     701             : 
     702             : static void
     703             : event_snap( fd_snapsv_t *       ctx,
     704             :             fd_stem_context_t * stem,
     705             :             uint                conn_idx,
     706             :             long                now, /* unix nanos */
     707             :             int                 som,
     708         336 :             int                 eom ) {
     709         336 :   if( FD_UNLIKELY( ctx->out.out_idx < 0L ) ) return;
     710         336 :   event_prepare( ctx, conn_idx );
     711         336 :   event_publish( ctx, stem, now, som, eom );
     712         336 : }
     713             : 
     714             : /* prep_accept enqueues an io_uring conn accept SQE. */
     715             : 
     716             : static void
     717         324 : prep_accept( fd_snapsv_t * ctx ) {
     718         324 :   FD_CHECK_ERR( !ctx->accept_inflight, "accept already inflight" );
     719         324 :   FD_CHECK_ERR( ctx->conn_cnt < ctx->conn_max, "conn table full" );
     720             : 
     721         324 :   uint conn_idx = conn_alloc( ctx );
     722         324 :   FD_CHECK_CRIT( conn_idx!=UINT_MAX, "conn free list empty" );
     723         324 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     724         324 :   conn->state = CONN_STATE_ACCEPT;
     725             : 
     726         324 :   uint fd_idx = ctx->conn0_fd_idx + conn_idx;
     727         324 :   ctx->accept_addr_len = sizeof(ctx->accept_addr);
     728             : 
     729         324 :   fd_io_uring_t * ring = ctx->ring;
     730         324 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
     731         324 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     732         324 :   *sqe = (fd_io_uring_sqe_t) {
     733         324 :     .opcode     = FD_IORING_OP_ACCEPT,
     734         324 :     .flags      = FD_IOSQE_FIXED_FILE,
     735         324 :     .fd         = FIXED_FD_LISTEN,
     736         324 :     .off        = (ulong)&ctx->accept_addr_len,
     737         324 :     .addr       = (ulong)&ctx->accept_addr,
     738         324 :     .len        = 0,
     739         324 :     .file_index = fd_idx + 1U /* registered files are 1-indexed */
     740         324 :   };
     741         324 :   snapsv_udata_t udata = { .op = UDATA_OP_ACCEPT, .conn_idx = conn_idx };
     742         324 :   sqe->user_data = udata.user_data;
     743         324 :   ctx->accept_inflight = 1;
     744         324 : }
     745             : 
     746             : /* conn_iobuf returns a pointer to the current I/O buffer of a conn. */
     747             : 
     748             : FD_FN_PURE static uchar *
     749             : conn_iobuf( fd_snapsv_t const *   ctx,
     750         972 :             snapsv_conn_t const * conn ) {
     751         972 :   FD_CHECK_CRIT( conn->iobuf_idx!=UINT_MAX, "conn has no iobuf" );
     752         972 :   return ctx->iobuf0 + (ulong)conn->iobuf_idx*ctx->iobuf_sz;
     753         972 : }
     754             : 
     755             : /* prep_peek enqueues a receive op to copy any newly arrived bytes into
     756             :    iobuf.  Does not advance the socket's file pointer. */
     757             : 
     758             : static void
     759             : prep_peek( fd_snapsv_t * ctx,
     760         222 :            uint          conn_idx ) {
     761         222 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     762         222 :   long now = fd_log_wallclock();
     763         222 :   if( FD_UNLIKELY( !conn->active_ts ) ) {
     764         162 :     conn->active_ts = now;
     765         162 :   }
     766         222 :   long idle_rem = fd_long_max( 1L, ctx->idle_timeout_nanos - (now - conn->active_ts) );
     767         222 :   conn->idle_timeout = (fd_kernel_timespec_t) {
     768         222 :     .tv_sec  = idle_rem / (long)1e9,
     769         222 :     .tv_nsec = idle_rem % (long)1e9
     770         222 :   };
     771         222 :   uint            len  = ctx->iobuf_sz - conn->req.len;
     772         222 :   FD_CHECK_CRIT( len, "request head buffer is full" );
     773         222 :   iobuf_alloc( ctx, &conn->iobuf_idx );
     774             : 
     775         222 :   fd_io_uring_t * ring = ctx->ring;
     776         222 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
     777         222 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     778         222 :   *sqe = (fd_io_uring_sqe_t) {
     779         222 :     .opcode    = FD_IORING_OP_RECV,
     780         222 :     .flags     = FD_IOSQE_FIXED_FILE | FD_IOSQE_IO_LINK,
     781             :     //.ioprio    = FD_IORING_RECVSEND_FIXED_BUF,
     782         222 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     783         222 :     .addr      = (ulong)( conn_iobuf( ctx, conn ) + conn->req.len ),
     784         222 :     .len       = len,
     785             :     //.buf_index = 0U,
     786         222 :     .msg_flags = MSG_PEEK
     787         222 :   };
     788         222 :   snapsv_udata_t udata = { .op = UDATA_OP_PEEK, .conn_idx = conn_idx };
     789         222 :   sqe->user_data = udata.user_data;
     790             : 
     791         222 :   sqe = fd_io_uring_get_sqe( ring->sq );
     792         222 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     793         222 :   *sqe = (fd_io_uring_sqe_t) {
     794         222 :     .opcode = FD_IORING_OP_LINK_TIMEOUT,
     795         222 :     .flags  = FD_IOSQE_FIXED_FILE, /* ignored */
     796         222 :     .addr   = (ulong)&conn->idle_timeout,
     797         222 :     .len    = 1U
     798         222 :   };
     799         222 :   udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
     800         222 :   sqe->user_data = udata.user_data;
     801         222 : }
     802             : 
     803             : /* conn_req_next discards request state at the end of a request.  Prepares
     804             :    for receipt of the next request (HTTP/1.1 keep-alive). */
     805             : 
     806             : static void
     807             : conn_req_next( fd_snapsv_t * ctx,
     808          57 :                uint          conn_idx ) {
     809          57 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     810          57 :   memset( &conn->req,  0, sizeof(conn->req ) );
     811          57 :   memset( &conn->res,  0, sizeof(conn->res ) );
     812          57 :   memset( &conn->snap, 0, sizeof(conn->snap) );
     813          57 :   conn->snap.key = (snap_key_t){ ULONG_MAX, ULONG_MAX };
     814          57 :   conn->state = CONN_STATE_REQ_PEEK;
     815          57 :   prep_peek( ctx, conn_idx );
     816          57 : }
     817             : 
     818             : /* prep_consume consumes previously received (via peek) iobuf bytes. */
     819             : 
     820             : static void
     821             : prep_consume( fd_snapsv_t * ctx,
     822             :               uint          conn_idx,
     823             :               uint          len,
     824         183 :               uchar         op ) {
     825         183 :   FD_CHECK_CRIT( len, "consuming zero bytes" );
     826         183 :   fd_io_uring_t * ring = ctx->ring;
     827         183 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
     828         183 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
     829         183 :   *sqe = (fd_io_uring_sqe_t) {
     830         183 :     .opcode    = FD_IORING_OP_RECV,
     831         183 :     .flags     = FD_IOSQE_FIXED_FILE,
     832         183 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
     833         183 :     .addr      = 0UL,
     834         183 :     .len       = len,
     835         183 :     .msg_flags = MSG_TRUNC | MSG_WAITALL
     836         183 :   };
     837         183 :   snapsv_udata_t udata = { .op = op, .conn_idx = conn_idx };
     838         183 :   sqe->user_data = udata.user_data;
     839         183 : }
     840             : 
     841             : /* conn_close destroys a conn object.  If no accept op is in-flight,
     842             :    enqueues one. */
     843             : 
     844             : static void
     845             : conn_close( fd_snapsv_t *       ctx,
     846             :             fd_stem_context_t * stem,
     847             :             uint                conn_idx,
     848          75 :             long                now ) {
     849          75 :   FD_CHECK_CRIT( conn_idx < ctx->conn_max, "invalid conn_idx" );
     850          75 :   snapsv_conn_t * conn   = &ctx->conn0[ conn_idx ];
     851             : 
     852          75 :   if( conn->req.get_snap ) {
     853             :     /* the transfer is ending before the whole range was sent, so this is
     854             :        not a clean completion unless a more specific reason was recorded */
     855           9 :     if( conn->res.close_kind==FD_SNAPSV_CLOSE_DONE &&
     856           9 :         conn->snap.req_cur < conn->snap.req_off1 ) {
     857           0 :       conn->res.close_kind = FD_SNAPSV_CLOSE_NET;
     858           0 :     }
     859           9 :     event_snap( ctx, stem, conn_idx, now, 0, /* eom */ 1 );
     860           9 :     conn->req.get_snap = 0;
     861           9 :   }
     862             : 
     863             : #ifndef FD_TILE_TEST
     864             :   /* io_uring OP_CLOSE on a fixed file does not use IOSQE_FIXED_FILE,
     865             :      therefore would break the io_uring sandbox (restrictions). */
     866             :   uint fd_idx = ctx->conn0_fd_idx + conn_idx;
     867             :   int unreg = -1;
     868           0 :   if( FD_UNLIKELY( fd_io_uring_register_files_update( ctx->ring->ioring_fd, fd_idx, &unreg, 1U )<0 ) ) {
     869           0 :     FD_LOG_ERR(( "io_uring_register(IORING_REGISTER_FILES_UPDATE) failed (%i-%s)", errno, fd_io_strerror( errno ) ));
     870           0 :   }
     871           0 : #endif
     872             : 
     873             :   /* conn no longer considered active */
     874          75 :   FD_CHECK_CRIT( ctx->conn_cnt, "conn count underflow" );
     875          75 :   ctx->conn_cnt--;
     876             : 
     877             :   /* return dead conn object */
     878          75 :   conn_free( ctx, conn_idx );
     879             : 
     880             :   /* allocate a new conn object, and schedule an accept() for it */
     881          75 :   if( FD_UNLIKELY( !ctx->accept_inflight ) ) {
     882           0 :     prep_accept( ctx );
     883           0 :   }
     884          75 : }
     885             : 
     886             : /* conn_req_done finishes a response that has no more bytes to send.
     887             :    Closes the conn if the client asked for a close, otherwise waits for
     888             :    the next request. */
     889             : 
     890             : static void
     891             : conn_req_done( fd_snapsv_t *       ctx,
     892             :                fd_stem_context_t * stem,
     893             :                uint                conn_idx,
     894          69 :                long                now ) {
     895          69 :   if( FD_UNLIKELY( ctx->conn0[ conn_idx ].sick ) ) {
     896          12 :     conn_close( ctx, stem, conn_idx, now );
     897          12 :     return;
     898          12 :   }
     899          57 :   ctx->conn0[ conn_idx ].active_ts = now;
     900          57 :   conn_req_next( ctx, conn_idx );
     901          57 : }
     902             : 
     903             : static ulong
     904             : populate_allowed_fds( fd_topo_t const *      topo,
     905             :                       fd_topo_tile_t const * tile,
     906             :                       ulong                  out_fds_cnt,
     907           0 :                       int *                  out_fds ) {
     908           0 :   fd_snapsv_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     909           0 :   if( FD_UNLIKELY( out_fds_cnt<4+ctx->snap_max ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
     910           0 :   ulong out_cnt = 0UL;
     911           0 :   out_fds[ out_cnt++ ] = 2; /* stderr */
     912           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
     913           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
     914           0 :   out_fds[ out_cnt++ ] = ctx->ring->ioring_fd;
     915           0 :   out_fds[ out_cnt++ ] = ctx->listen_fd;
     916           0 :   for( ulong i=0UL; i<ctx->snap_max; i++ )
     917           0 :     out_fds[ out_cnt++ ] = FD_SNAP_RO_FD( i ); /* snapshot pool */
     918           0 :   return out_cnt;
     919           0 : }
     920             : 
     921             : static ulong
     922             : populate_allowed_seccomp( fd_topo_t const *      topo,
     923             :                           fd_topo_tile_t const * tile,
     924             :                           ulong                  out_cnt,
     925           0 :                           struct sock_filter *   out ) {
     926           0 :   fd_snapsv_t * ctx = fd_topo_obj_laddr( topo, tile->tile_obj_id );
     927           0 :   FD_CHECK_ERR( ctx->snap_max, "snapsv snap_max is zero" );
     928           0 :   populate_sock_filter_policy_fd_snapsv_tile(
     929           0 :       out_cnt, out,
     930           0 :       (uint)fd_log_private_logfile_fd(),
     931           0 :       (uint)ctx->ring->ioring_fd,
     932           0 :       (uint)FD_SNAP_RO_FD( 0 ),
     933           0 :       (uint)FD_SNAP_RO_FD( ctx->snap_max-1UL ) );
     934           0 :   return sock_filter_policy_fd_snapsv_tile_instr_cnt;
     935           0 : }
     936             : 
     937             : static ulong
     938             : rlimit_file_cnt( fd_topo_t const *      topo,
     939           0 :                  fd_topo_tile_t const * tile ) {
     940           0 :   (void)topo;
     941             :   /* stderr, logfile, boot control pipe, io_uring, listen socket, one
     942             :      spare for accept, the snapshot pool, and active connections */
     943           0 :   return 6UL + tile->snapsv.snap_max + tile->snapsv.conn_max;
     944           0 : }
     945             : 
     946             : /* handle_accept handles the completion of an accept op. */
     947             : 
     948             : static void
     949             : handle_accept( fd_snapsv_t * ctx,
     950             :                uint          conn_idx,
     951             :                int           res,
     952         162 :                long          now ) {
     953         162 :   ctx->accept_inflight = 0;
     954             : 
     955         162 :   if( FD_UNLIKELY( res<0 ) ) {
     956           0 :     FD_LOG_WARNING(( "accept() failed (%i-%s)", -res, fd_io_strerror( -res ) ));
     957           0 :     conn_free( ctx, conn_idx );
     958           0 :     prep_accept( ctx );
     959           0 :     return;
     960           0 :   }
     961             : 
     962         162 :   ctx->conn_cnt++;
     963             : 
     964             :   /* set conn addr */
     965         162 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
     966         162 :   switch( ctx->accept_addr.ss_family ) {
     967         162 :   case AF_INET: {
     968         162 :     struct sockaddr_in const * addr = fd_type_pun_const( &ctx->accept_addr );
     969         162 :     fd_ip6_addr_ip4_mapped( conn->peer_ip.addr, addr->sin_addr.s_addr );
     970         162 :     conn->peer_ip.scope_id = 0U;
     971         162 :     conn->peer_port = fd_ushort_bswap( addr->sin_port );
     972         162 :     break;
     973           0 :   }
     974           0 :   case AF_INET6: {
     975           0 :     struct sockaddr_in6 const * addr = fd_type_pun_const( &ctx->accept_addr );
     976           0 :     memcpy( conn->peer_ip.addr, addr->sin6_addr.s6_addr, 16UL );
     977           0 :     conn->peer_ip.scope_id = addr->sin6_scope_id;
     978           0 :     conn->peer_port = fd_ushort_bswap( addr->sin6_port );
     979           0 :     break;
     980           0 :   }
     981           0 :   default:
     982           0 :     FD_LOG_WARNING(( "accept() returned unknown address family %i", (int)ctx->accept_addr.ss_family ));
     983           0 :     memset( &conn->peer_ip, 0, sizeof(conn->peer_ip) );
     984           0 :     conn->peer_port = 0U;
     985         162 :   }
     986             : 
     987         162 :   conn->active_ts = now;
     988         162 :   conn->state = CONN_STATE_REQ_PEEK;
     989             : 
     990         162 :   prep_peek( ctx, conn_idx );
     991         162 :   if( FD_LIKELY( ctx->conn_cnt<ctx->conn_max ) ) {
     992         162 :     prep_accept( ctx );
     993         162 :   }
     994         162 : }
     995             : 
     996             : /* conn_snap_entry checks that the snapshot for the current operation is
     997             :    still valid.  The return value of this function is invalidated once
     998             :    after_credit returns. */
     999             : 
    1000             : static snap_entry_t *
    1001             : conn_snap_entry( fd_snapsv_t *   ctx,
    1002         621 :                  snapsv_conn_t * conn ) {
    1003         621 :   FD_CHECK_ERR( conn->snap.key.slot!=ULONG_MAX, "no snapshot selected" );
    1004         621 :   snap_entry_t * entry = snap_map_update( ctx->snap_map, &conn->snap.key );
    1005         621 :   if( FD_UNLIKELY( !entry || entry->sz!=conn->snap.file_sz ) ) return NULL;
    1006         546 :   return entry;
    1007         621 : }
    1008             : 
    1009             : static int
    1010             : snap_lock( snap_entry_t * entry );
    1011             : 
    1012             : /* shovel dispatches snapshot streaming I/O work.
    1013             :    This forms a 2-job deep pipeline that attempts to concurrently do
    1014             :    disk read and network write I/O. */
    1015             : 
    1016             : static void
    1017             : shovel( fd_snapsv_t *       ctx,
    1018             :         fd_stem_context_t * stem,
    1019             :         uint                conn_idx,
    1020         516 :         long                now ) {
    1021         516 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1022         516 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_SHOVEL, "conn state confusion" );
    1023             : 
    1024         516 :   if( FD_UNLIKELY( conn->closing ) ) {
    1025           9 :     if( !conn->disk_inflight && !conn->net_inflight ) {
    1026           9 :       conn_close( ctx, stem, conn_idx, now );
    1027           9 :     }
    1028           9 :     return;
    1029           9 :   }
    1030             : 
    1031         507 :   if( conn->iobuf_idx!=UINT_MAX &&
    1032         507 :       conn->res.sent==conn->res.len &&
    1033         507 :       !conn->net_inflight ) {
    1034         237 :     iobuf_free( ctx, &conn->iobuf_idx );
    1035         237 :     conn->res.sent = 0U;
    1036         237 :     conn->res.len  = 0U;
    1037         237 :   }
    1038             : 
    1039             :   /* disk read buffer complete, rename to net send buffer */
    1040         507 :   if( conn->iobuf_idx==UINT_MAX && conn->rdbuf_len ) {
    1041         237 :     FD_CHECK_CRIT( conn->rdbuf_idx!=UINT_MAX, "completed read has no buffer" );
    1042         237 :     conn->iobuf_idx = conn->rdbuf_idx;
    1043         237 :     conn->rdbuf_idx = UINT_MAX;
    1044         237 :     conn->res.sent  = 0U;
    1045         237 :     conn->res.len   = conn->rdbuf_len;
    1046         237 :     conn->rdbuf_len = 0U;
    1047         237 :   }
    1048             : 
    1049             :   /* generate net send SQE */
    1050         507 :   if( conn->iobuf_idx!=UINT_MAX && conn->res.sent<conn->res.len && !conn->net_inflight ) {
    1051         237 :     fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ctx->ring->sq );
    1052         237 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1053         237 :     *sqe = (fd_io_uring_sqe_t) {
    1054         237 :       .opcode    = FD_IORING_OP_SEND,
    1055         237 :       .flags     = FD_IOSQE_FIXED_FILE | FD_IOSQE_IO_LINK,
    1056             :       //.ioprio    = FD_IORING_RECVSEND_FIXED_BUF,
    1057         237 :       .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
    1058         237 :       .addr      = (ulong)conn_iobuf( ctx, conn ) + conn->res.sent,
    1059         237 :       .len       = conn->res.len - conn->res.sent,
    1060             :       //.buf_index = 0U,
    1061         237 :       .msg_flags = MSG_NOSIGNAL
    1062         237 :     };
    1063         237 :     snapsv_udata_t udata = { .op = UDATA_OP_SHOVEL_NET, .conn_idx = conn_idx };
    1064         237 :     sqe->user_data = udata.user_data;
    1065         237 :     conn->net_inflight = 1U;
    1066             : 
    1067         237 :     sqe = fd_io_uring_get_sqe( ctx->ring->sq );
    1068         237 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1069         237 :     *sqe = (fd_io_uring_sqe_t) {
    1070         237 :       .opcode = FD_IORING_OP_LINK_TIMEOUT,
    1071         237 :       .flags  = FD_IOSQE_FIXED_FILE, /* ignored */
    1072         237 :       .addr   = (ulong)&ctx->send_timeout,
    1073         237 :       .len    = 1U
    1074         237 :     };
    1075         237 :     udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
    1076         237 :     sqe->user_data = udata.user_data;
    1077         237 :   }
    1078             : 
    1079             :   /* generate disk read SQE */
    1080         507 :   if( conn->rdbuf_idx==UINT_MAX &&
    1081         507 :       !conn->disk_inflight      &&
    1082         507 :       conn->snap.req_cur < conn->snap.req_off1 ) {
    1083         246 :     snap_entry_t const * snap = conn_snap_entry( ctx, conn );
    1084         246 :     if( FD_UNLIKELY( !snap ) ) {
    1085           0 :       conn->res.close_kind = FD_SNAPSV_CLOSE_ABORT;
    1086           0 :       conn->closing        = 1U;
    1087           0 :       shovel( ctx, stem, conn_idx, now );
    1088           0 :       return;
    1089           0 :     }
    1090             : 
    1091         246 :     iobuf_alloc( ctx, &conn->rdbuf_idx );
    1092         246 :     ulong read_sz = fd_ulong_min( ctx->iobuf_sz, conn->snap.req_off1-conn->snap.req_cur );
    1093         246 :     ulong pool_idx = (ulong)( snap->fd - FD_SNAP_RO_FD( 0 ) );
    1094         246 :     FD_CHECK_CRIT( pool_idx<ctx->snap_max, "snapshot has invalid file descriptor" );
    1095             : 
    1096         246 :     fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ctx->ring->sq );
    1097         246 :     FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1098         246 :     *sqe = (fd_io_uring_sqe_t) {
    1099         246 :       .opcode    = FD_IORING_OP_READ_FIXED,
    1100         246 :       .flags     = FD_IOSQE_FIXED_FILE,
    1101         246 :       .fd        = (int)( FIXED_FD_CNT + pool_idx ),
    1102         246 :       .off       = conn->snap.req_cur,
    1103         246 :       .addr      = (ulong)( ctx->iobuf0 + (ulong)conn->rdbuf_idx*ctx->iobuf_sz ),
    1104         246 :       .len       = (uint)read_sz,
    1105         246 :       .buf_index = 0U
    1106         246 :     };
    1107         246 :     snapsv_udata_t udata = { .op = UDATA_OP_SHOVEL_DISK, .conn_idx = conn_idx };
    1108         246 :     sqe->user_data = udata.user_data;
    1109         246 :     conn->disk_inflight = 1U;
    1110         246 :   }
    1111             : 
    1112             :   /* everything done */
    1113         507 :   if( FD_UNLIKELY( conn->snap.req_cur>=conn->snap.req_off1 &&
    1114         507 :                    conn->iobuf_idx==UINT_MAX &&
    1115         507 :                    conn->rdbuf_idx==UINT_MAX &&
    1116         507 :                    !conn->disk_inflight &&
    1117         507 :                    !conn->net_inflight ) ) {
    1118          24 :     conn->res.close_kind = FD_SNAPSV_CLOSE_DONE;
    1119          24 :     event_snap( ctx, stem, conn_idx, now, 0, 1 ); /* eom */
    1120          24 :     conn->req.get_snap = 0;
    1121          24 :     conn_req_done( ctx, stem, conn_idx, now );
    1122          24 :   }
    1123         507 : }
    1124             : 
    1125             : /* shovel_comp_disk reacts to a snapshot streaming disk read completion. */
    1126             : 
    1127             : static void
    1128             : shovel_comp_disk( fd_snapsv_t *       ctx,
    1129             :                   fd_stem_context_t * stem,
    1130             :                   uint                conn_idx,
    1131             :                   int                 res,
    1132         246 :                   long                now ) {
    1133         246 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1134         246 :   FD_CHECK_CRIT( conn->state==CONN_STATE_RES_SHOVEL && conn->disk_inflight,
    1135         246 :                  "unexpected shovel disk completion" );
    1136         246 :   conn->disk_inflight = 0U;
    1137             : 
    1138             :   /* snapshot file recycled between submission and completion? */
    1139         246 :   if( FD_UNLIKELY( !conn_snap_entry( ctx, conn ) ) ) {
    1140           9 :     conn->res.close_kind = FD_SNAPSV_CLOSE_ABORT;
    1141           9 :     conn->closing        = 1U;
    1142           9 :     shovel( ctx, stem, conn_idx, now );
    1143           9 :     return;
    1144           9 :   }
    1145             : 
    1146         237 :   ulong rem     = conn->snap.req_off1 - conn->snap.req_cur;
    1147         237 :   ulong read_sz = fd_ulong_min( ctx->iobuf_sz, rem );
    1148         237 :   if( FD_UNLIKELY( res<=0 || (ulong)res>read_sz ) ) {
    1149           0 :     if( res<0 ) {
    1150           0 :       FD_LOG_WARNING(( "snapshot read failed (%i-%s)", -res, fd_io_strerror( -res ) ));
    1151           0 :     } else if( !res ) {
    1152           0 :       FD_LOG_WARNING(( "snapshot file ended early" ));
    1153           0 :     } else {
    1154           0 :       FD_LOG_WARNING(( "snapshot read returned too many bytes (%i>%lu)", res, read_sz ));
    1155           0 :     }
    1156           0 :     conn->res.close_kind = FD_SNAPSV_CLOSE_ABORT;
    1157           0 :     conn->closing        = 1U;
    1158           0 :     shovel( ctx, stem, conn_idx, now );
    1159           0 :     return;
    1160           0 :   }
    1161             : 
    1162         237 :   conn->snap.req_cur += (ulong)res;
    1163         237 :   conn->rdbuf_len     = (uint)res;
    1164         237 :   shovel( ctx, stem, conn_idx, now );
    1165         237 : }
    1166             : 
    1167             : /* shovel_comp_net reacts to a snapshot streaming network write
    1168             :    completion. */
    1169             : 
    1170             : static void
    1171             : shovel_comp_net( fd_snapsv_t *       ctx,
    1172             :                  fd_stem_context_t * stem,
    1173             :                  uint                conn_idx,
    1174             :                  int                 res,
    1175         237 :                  long                now ) {
    1176         237 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1177         237 :   FD_CHECK_CRIT( conn->state==CONN_STATE_RES_SHOVEL, "unexpected shovel net completion" );
    1178         237 :   FD_CHECK_CRIT( conn->net_inflight, "unexpected SEND completion" );
    1179         237 :   conn->net_inflight = 0U;
    1180             : 
    1181         237 :   if( FD_UNLIKELY( res<=0 || (uint)res>conn->res.len-conn->res.sent ) ) {
    1182           0 :     FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
    1183           0 :     if( res<0 ) {
    1184           0 :       FD_LOG_INFO(( "snapshot download peer %s:%u: snapshot data send error (%i-%s)",
    1185           0 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
    1186           0 :     }
    1187           0 :     conn->res.close_kind = FD_SNAPSV_CLOSE_NET;
    1188           0 :     conn->closing        = 1U;
    1189           0 :     shovel( ctx, stem, conn_idx, now );
    1190           0 :     return;
    1191           0 :   }
    1192             : 
    1193         237 :   conn->res.sent      += (uint)res;
    1194         237 :   conn->snap.req_sent += (ulong)res;
    1195         237 :   conn->active_ts      = now;
    1196         237 :   FD_MCNT_INC( SNAPSV, HTTP_TX_BYTES, (ulong)res );
    1197         237 :   shovel( ctx, stem, conn_idx, now );
    1198         237 : }
    1199             : 
    1200             : /* prep_write_hdr enqueues a header write op. */
    1201             : 
    1202             : static void
    1203             : prep_write_hdr( fd_snapsv_t * ctx,
    1204         165 :                 uint          conn_idx ) {
    1205         165 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1206         165 :   fd_io_uring_t * ring = ctx->ring;
    1207         165 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
    1208         165 :   FD_CHECK_ERR( conn->res.sent < conn->res.len, "conn res state confusion" );
    1209         165 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1210         165 :   *sqe = (fd_io_uring_sqe_t) {
    1211         165 :     .opcode    = FD_IORING_OP_SEND,
    1212         165 :     .flags     = FD_IOSQE_FIXED_FILE | FD_IOSQE_IO_LINK,
    1213             :     //.ioprio    = FD_IORING_RECVSEND_FIXED_BUF,
    1214         165 :     .fd        = (int)( ctx->conn0_fd_idx + conn_idx ),
    1215         165 :     .addr      = (ulong)conn_iobuf( ctx, conn ) + conn->res.sent,
    1216         165 :     .len       = conn->res.len - conn->res.sent,
    1217             :     //.buf_index = 0U,
    1218         165 :     .msg_flags = MSG_NOSIGNAL
    1219         165 :   };
    1220         165 :   snapsv_udata_t udata = { .op = UDATA_OP_WRITE_HDR, .conn_idx = conn_idx };
    1221         165 :   sqe->user_data = udata.user_data;
    1222             : 
    1223         165 :   sqe = fd_io_uring_get_sqe( ring->sq );
    1224         165 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1225         165 :   *sqe = (fd_io_uring_sqe_t) {
    1226         165 :     .opcode = FD_IORING_OP_LINK_TIMEOUT,
    1227         165 :     .flags  = FD_IOSQE_FIXED_FILE, /* ignored */
    1228         165 :     .addr   = (ulong)&ctx->send_timeout,
    1229         165 :     .len    = 1U
    1230         165 :   };
    1231         165 :   udata = (snapsv_udata_t){ .op = UDATA_OP_TIMEOUT };
    1232         165 :   sqe->user_data = udata.user_data;
    1233         165 : }
    1234             : 
    1235             : static ulong
    1236             : build_err_hdr( char  out[ static RES_HDR_MAX ], /* not a cstr */
    1237             :                uint  http_err,
    1238             :                ulong object_sz,  /* 'Content-Range' hint */
    1239         114 :                int   sick ) {    /* set 'Connection: close'? */
    1240         114 :   char * p = fd_cstr_init( out );
    1241         114 :   p = fd_cstr_append_cstr( p, "HTTP/1.1 ");
    1242         114 :   switch( http_err ) {
    1243          30 :   case 400: p = fd_cstr_append_cstr( p, "400 Bad Request"           ); break;
    1244          60 :   case 404: p = fd_cstr_append_cstr( p, "404 Not Found"             ); break;
    1245          18 :   case 416: p = fd_cstr_append_cstr( p, "416 Range Not Satisfiable" ); break;
    1246           6 :   default:  p = fd_cstr_append_cstr( p, "500 Internal Server Error" ); break;
    1247         114 :   }
    1248         114 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1249         114 :   if( http_err==416 && object_sz!=ULONG_MAX ) {
    1250          15 :     p = fd_cstr_append_cstr( p, "Content-Range: bytes */" );
    1251          15 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, object_sz, fd_ulong_base10_dig_cnt( object_sz ) );
    1252          15 :     p = fd_cstr_append_cstr( p, "\r\n" );
    1253          15 :   }
    1254         114 :   p = fd_cstr_append_cstr( p, "Content-Length: 0\r\n" );
    1255         114 :   if( sick ) {
    1256          39 :     p = fd_cstr_append_cstr( p, "Connection: close\r\n" );
    1257          39 :   }
    1258         114 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1259         114 :   return (ulong)( p - out );
    1260         114 : }
    1261             : 
    1262             : /* serve_http_err dispatches a RES_WRITE_HDR job for a generic HTTP
    1263             :    error page. */
    1264             : 
    1265             : static void
    1266             : serve_http_err( fd_snapsv_t * ctx,
    1267          84 :                 uint          conn_idx ) {
    1268          84 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1269          84 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_WRITE_ERR, "state confusion" );
    1270          84 :   iobuf_alloc( ctx, &conn->iobuf_idx );
    1271          84 :   uchar * iobuf = conn_iobuf( ctx, conn );
    1272          84 :   snap_entry_t const * snap = conn->snap.key.slot!=ULONG_MAX ? conn_snap_entry( ctx, conn ) : NULL;
    1273          84 :   conn->res.sent = 0;
    1274          84 :   conn->res.len  = (uint)build_err_hdr(
    1275          84 :       (char *)iobuf,
    1276          84 :       conn->res.status,
    1277          84 :       snap ? snap->sz : ULONG_MAX,
    1278          84 :       conn->sick );
    1279          84 :   prep_write_hdr( ctx, conn_idx );
    1280          84 : }
    1281             : 
    1282             : static ulong
    1283             : build_snap_res_hdr( char  out[ static RES_HDR_MAX ],
    1284             :                     int   range,
    1285             :                     int   is_zstd,
    1286             :                     ulong range0,
    1287             :                     ulong range1,
    1288             :                     ulong object_sz,
    1289          81 :                     int   sick ) {    /* set 'Connection: close'? */
    1290          81 :   char * p = fd_cstr_init( out );
    1291          81 :   if( range ) {
    1292          33 :     p = fd_cstr_append_cstr( p, "HTTP/1.1 206 Partial Content\r\n" );
    1293          48 :   } else {
    1294          48 :     p = fd_cstr_append_cstr( p, "HTTP/1.1 200 OK\r\n" );
    1295          48 :   }
    1296          81 :   if( is_zstd ) {
    1297          72 :     p = fd_cstr_append_cstr( p, "Content-Type: application/zstd\r\n" );
    1298          72 :   } else {
    1299           9 :     p = fd_cstr_append_cstr( p, "Content-Type: application/x-tar\r\n" );
    1300           9 :   }
    1301          81 :   p = fd_cstr_append_cstr( p, "Accept-Ranges: bytes\r\n" );
    1302          81 :   if( range ) {
    1303          33 :     p = fd_cstr_append_cstr( p, "Content-Range: bytes " );
    1304          33 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, range0, fd_ulong_base10_dig_cnt( range0 ) );
    1305          33 :     p = fd_cstr_append_char( p, '-' );
    1306          33 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, range1-1UL, fd_ulong_base10_dig_cnt( range1-1UL ) );
    1307          33 :     p = fd_cstr_append_char( p, '/' );
    1308          33 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, object_sz, fd_ulong_base10_dig_cnt( object_sz ) );
    1309          33 :     p = fd_cstr_append_cstr( p, "\r\n" );
    1310          33 :   }
    1311          81 :   p = fd_cstr_append_cstr( p, "Content-Length: " );
    1312          81 :   ulong content_len = range1 - range0;
    1313          81 :   p = fd_cstr_append_ulong_as_text( p, 0, 0, content_len, fd_ulong_base10_dig_cnt( content_len ) );
    1314          81 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1315          81 :   if( sick ) {
    1316          12 :     p = fd_cstr_append_cstr( p, "Connection: close\r\n" );
    1317          12 :   }
    1318          81 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1319          81 :   fd_cstr_fini( p );
    1320          81 :   return (ulong)( p - out );
    1321          81 : }
    1322             : 
    1323             : /* serve_snap_res_hdr dispatches a RES_WRITE_HDR job for a snapshot GET
    1324             :    or HEAD request. */
    1325             : 
    1326             : static void
    1327             : serve_snap_res_hdr( fd_snapsv_t *       ctx,
    1328             :                     fd_stem_context_t * stem,
    1329             :                     uint                conn_idx,
    1330          54 :                     long                now ) {
    1331          54 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1332          54 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_WRITE_HDR, "state confusion" );
    1333             : 
    1334          54 :   snap_entry_t * snap = conn_snap_entry( ctx, conn );
    1335          54 :   if( FD_UNLIKELY( !snap ) ) {
    1336             :     /* rare edge case: snap was deleted by the snapmk tile just after
    1337             :        the user requested it.  Don't bother returning an error, just
    1338             :        abort the conn. */
    1339           0 :     conn_close( ctx, stem, conn_idx, now );
    1340           0 :     return;
    1341           0 :   }
    1342          54 :   if( FD_UNLIKELY( !snap_lock( snap ) ) ) {
    1343           0 :     conn_close( ctx, stem, conn_idx, now );
    1344           0 :     return;
    1345           0 :   }
    1346             :   /* return response header */
    1347          54 :   FD_CHECK_ERR( conn->iobuf_idx==UINT_MAX, "conn has stale iobuf" );
    1348          54 :   iobuf_alloc( ctx, &conn->iobuf_idx );
    1349          54 :   uchar * iobuf = conn_iobuf( ctx, conn );
    1350          54 :   conn->res.sent   = 0;
    1351          54 :   conn->res.len    = (uint)build_snap_res_hdr(
    1352          54 :       (char *)iobuf,
    1353          54 :       conn->snap.range,
    1354          54 :       snap->is_zstd,
    1355          54 :       conn->snap.req_off0, conn->snap.req_off1,
    1356          54 :       snap->sz,
    1357          54 :       conn->sick );
    1358          54 :   conn->res.hdr_ts = now;
    1359          54 :   prep_write_hdr( ctx, conn_idx );
    1360          54 : }
    1361             : 
    1362             : /* newest_snap returns the newest snapshot of the requested type. */
    1363             : 
    1364             : static snap_entry_t *
    1365             : newest_snap( fd_snapsv_t * ctx,
    1366         141 :              int           incremental ) {
    1367             : 
    1368         141 :   if( FD_UNLIKELY( !( incremental ? ctx->snap_cnt_incr : ctx->snap_cnt_full ) ) ) {
    1369          30 :     return NULL; /* no such snapshot of this type */
    1370          30 :   }
    1371         111 :   snap_entry_t * entry = incremental ? ctx->newest_incr : ctx->newest_full;
    1372         111 :   if( FD_UNLIKELY( !entry ) ) {
    1373          87 :     snap_entry_t * full = incremental ? newest_snap( ctx, 0 ) : NULL;
    1374          87 :     if( FD_UNLIKELY( incremental && !full ) ) return NULL;
    1375          84 :     ulong          base_slot = full ? full->key.slot : ULONG_MAX;
    1376          84 :     snap_entry_t * entry0    = snap_map_ele0( ctx->snap_map );
    1377          84 :     ulong          ele_max   = snap_map_ele_max( ctx->snap_map );
    1378        2100 :     for( ulong i=0UL; i<ele_max; i++ ) {
    1379        2016 :       snap_entry_t * e = &entry0[ i ];
    1380        2016 :       if( snap_map_ele_is_free( e ) ) continue;
    1381         258 :       if( e->key.base_slot!=base_slot ) continue;
    1382         126 :       if( entry && e->key.slot<=entry->key.slot ) continue;
    1383         114 :       entry = e;
    1384         114 :     }
    1385          84 :   }
    1386         108 :   *( incremental ? &ctx->newest_incr : &ctx->newest_full ) = entry;
    1387         108 :   if( FD_UNLIKELY( !entry ) ) return NULL;
    1388         102 :   return entry;
    1389         108 : }
    1390             : 
    1391             : static ulong
    1392             : build_redirect_hdr( char        out[ static RES_HDR_MAX ], /* not a cstr */
    1393             :                     int         incremental,
    1394             :                     ulong       slot,
    1395             :                     ulong       base_slot,
    1396             :                     uchar const hash[ static 32 ],
    1397             :                     int         is_zstd,
    1398          45 :                     int         sick ) {
    1399          45 :   char * p = fd_cstr_init( out );
    1400          45 :   p = fd_cstr_append_cstr( p, "HTTP/1.1 302 Found\r\n" );
    1401          45 :   p = fd_cstr_append_cstr( p, "Location: /" );
    1402          45 :   if( incremental ) {
    1403          21 :     p = fd_cstr_append_cstr( p, "incremental-snapshot-" );
    1404          21 :     p = fd_cstr_append_ulong_as_text( p, 0, 0, base_slot, fd_ulong_base10_dig_cnt( base_slot ) );
    1405          21 :     p = fd_cstr_append_char( p, '-' );
    1406          24 :   } else {
    1407          24 :     p = fd_cstr_append_cstr( p, "snapshot-" );
    1408          24 :   }
    1409          45 :   p = fd_cstr_append_ulong_as_text( p, 0, 0, slot, fd_ulong_base10_dig_cnt( slot ) );
    1410          45 :   p = fd_cstr_append_char( p, '-' );
    1411          45 :   char hash_b58[ FD_BASE58_ENCODED_32_SZ ];
    1412          45 :   fd_base58_encode_32( hash, NULL, hash_b58 );
    1413          45 :   p = fd_cstr_append_cstr( p, hash_b58 );
    1414          45 :   p = fd_cstr_append_cstr( p, is_zstd ? ".tar.zst\r\n" : ".tar\r\n" );
    1415          45 :   p = fd_cstr_append_cstr( p, "Content-Length: 0\r\n" );
    1416          45 :   if( sick ) {
    1417           6 :     p = fd_cstr_append_cstr( p, "Connection: close\r\n" );
    1418           6 :   }
    1419          45 :   p = fd_cstr_append_cstr( p, "\r\n" );
    1420          45 :   return (ulong)( p - out );
    1421          45 : }
    1422             : 
    1423             : /* serve_redirect dispatches a RES_WRITE_HDR job for a /snapshot.tar.bz2
    1424             :    redirect. */
    1425             : 
    1426             : static void
    1427             : serve_redirect( fd_snapsv_t * ctx,
    1428          39 :                 uint          conn_idx ) {
    1429          39 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1430          39 :   FD_CHECK_ERR( conn->state==CONN_STATE_RES_REDIRECT, "state confusion" );
    1431             : 
    1432          39 :   snap_entry_t * snap = newest_snap( ctx, conn->req.incremental );
    1433          39 :   if( FD_UNLIKELY( !snap ) ) {
    1434          12 :     conn->state      = CONN_STATE_RES_WRITE_ERR;
    1435          12 :     conn->res.status = 404U;
    1436          12 :     serve_http_err( ctx, conn_idx );
    1437          12 :     return;
    1438          12 :   }
    1439             : 
    1440             :   /* return response */
    1441          27 :   FD_CHECK_ERR( conn->iobuf_idx==UINT_MAX, "conn has stale iobuf" );
    1442          27 :   iobuf_alloc( ctx, &conn->iobuf_idx );
    1443          27 :   uchar * iobuf = conn_iobuf( ctx, conn );
    1444          27 :   conn->res.sent = 0;
    1445          27 :   conn->res.len  = (uint)build_redirect_hdr(
    1446          27 :       (char *)iobuf,
    1447          27 :       conn->req.incremental,
    1448          27 :       snap->key.slot, snap->key.base_slot,
    1449          27 :       snap->hash,
    1450          27 :       snap->is_zstd,
    1451          27 :       conn->sick );
    1452          27 :   prep_write_hdr( ctx, conn_idx );
    1453          27 : }
    1454             : 
    1455             : static void
    1456             : prep_peek( fd_snapsv_t * ctx,
    1457             :            uint          conn_idx );
    1458             : 
    1459             : /* handle_write_hdr_comp handles the completion of a header write op. */
    1460             : 
    1461             : static void
    1462             : handle_write_hdr_comp( fd_snapsv_t *       ctx,
    1463             :                        fd_stem_context_t * stem,
    1464             :                        uint                conn_idx,
    1465             :                        int                 res,
    1466          78 :                        long                now ) {
    1467          78 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1468          78 :   if( FD_UNLIKELY( res<0 ) ) {
    1469           0 :     if( res<0 ) {
    1470           0 :       FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
    1471           0 :       FD_LOG_INFO(( "snapshot download peer %s:%u: response header send error (%i-%s)",
    1472           0 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
    1473           0 :     }
    1474           0 :     conn_close( ctx, stem, conn_idx, now );
    1475           0 :     return;
    1476           0 :   }
    1477          78 :   conn->res.sent += (uint)res;
    1478          78 :   if( FD_LIKELY( conn->res.sent < conn->res.len ) ) {
    1479           0 :     prep_write_hdr( ctx, conn_idx );
    1480           0 :     return;
    1481           0 :   }
    1482             :   /* wrote response header */
    1483          78 :   iobuf_free( ctx, &conn->iobuf_idx );
    1484          78 :   switch( conn->state ) {
    1485          24 :   case CONN_STATE_RES_WRITE_ERR:
    1486          39 :   case CONN_STATE_RES_REDIRECT:
    1487             :     /* returned header-only response, handle the next request */
    1488          39 :     conn_req_done( ctx, stem, conn_idx, now );
    1489          39 :     return;
    1490          39 :   case CONN_STATE_RES_WRITE_HDR:
    1491          39 :     if( conn->req.head || conn->snap.key.slot==ULONG_MAX ) {
    1492           6 :       conn_req_done( ctx, stem, conn_idx, now );
    1493           6 :       return;
    1494           6 :     }
    1495             :     /* now serve the snapshot body */
    1496          33 :     conn->req.get_snap = 1; /* streaming begin */
    1497          33 :     event_snap( ctx, stem, conn_idx, now, 1, 0 ); /* som */
    1498          33 :     conn->state = CONN_STATE_RES_SHOVEL;
    1499          33 :     shovel( ctx, stem, conn_idx, now );
    1500          33 :     return;
    1501           0 :   default:
    1502           0 :     FD_LOG_CRIT(( "conn %u: state confusion", conn_idx ));
    1503          78 :   }
    1504          78 : }
    1505             : 
    1506             : /* match_snapshot_path parses a snapshot HTTP request path. */
    1507             : 
    1508             : static snap_key_t *
    1509             : match_snapshot_path( char const * path,
    1510             :                      ulong        path_len,
    1511             :                      snap_key_t * out,
    1512             :                      uchar        out_hash[ 32 ],
    1513         150 :                      int *        out_is_zstd ) {
    1514         150 :   if( path_len<9 ) return NULL;
    1515             : 
    1516             :   /* ugly: copy to cstr */
    1517         144 :   char cstr[ FD_SNAP_NAME_MAX ];
    1518         144 :   if( FD_UNLIKELY( path_len>=FD_SNAP_NAME_MAX-1 ) ) return NULL;
    1519         132 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( cstr ), path, path_len ) );
    1520             : 
    1521         132 :   ulong full_slot, incremental_slot;
    1522         132 :   int res = fd_ssarchive_parse_filename( cstr, &full_slot, &incremental_slot, out_hash, out_is_zstd );
    1523         132 :   if( FD_UNLIKELY( res!=0 ) ) return NULL;
    1524          99 :   *out = (snap_key_t) {
    1525          99 :     .slot      = incremental_slot!=ULONG_MAX ? incremental_slot : full_slot,
    1526          99 :     .base_slot = incremental_slot!=ULONG_MAX ? full_slot : ULONG_MAX
    1527          99 :   };
    1528          99 :   return out;
    1529         132 : }
    1530             : 
    1531             : /* parse_range_header parses a 'Range: bytes=start-end' header.
    1532             :    value points to the header value char[value_len].
    1533             :    object_sz is the size of the file, and [*out_range0, *out_range1)
    1534             :    is the requested range. */
    1535             : 
    1536             : static int
    1537             : parse_range_header( char const * value,
    1538             :                     ulong        value_len,
    1539             :                     ulong        object_sz,
    1540             :                     ulong *      out_range0,
    1541         309 :                     ulong *      out_range1 ) {
    1542         309 :   char const * p   = value;
    1543         309 :   char const * end = value + value_len;
    1544             : 
    1545         342 :   while( p<end && (*p==' ' || *p=='\t') ) p++;
    1546         330 :   while( end>p && (end[-1]==' ' || end[-1]=='\t') ) end--;
    1547         309 :   if( FD_UNLIKELY( (ulong)(end-p)<7UL || strncasecmp( p, "bytes=", 6UL ) ) ) return -1;
    1548         273 :   p += 6;
    1549             : 
    1550         273 :   ulong first = 0UL;
    1551         273 :   int have_first = 0;
    1552        1035 :   while( p<end && *p>='0' && *p<='9' ) {
    1553         774 :     uint digit = (uint)(*p-'0');
    1554         774 :     if( FD_UNLIKELY( first>(ULONG_MAX-digit)/10UL ) ) return -1;
    1555         762 :     first = first*10UL + digit;
    1556         762 :     have_first = 1;
    1557         762 :     p++;
    1558         762 :   }
    1559         261 :   if( FD_UNLIKELY( p==end || *p!='-' ) ) return -1;
    1560         234 :   p++;
    1561             : 
    1562         234 :   ulong last = 0UL;
    1563         234 :   int have_last = 0;
    1564         900 :   while( p<end && *p>='0' && *p<='9' ) {
    1565         678 :     uint digit = (uint)(*p-'0');
    1566         678 :     if( FD_UNLIKELY( last>(ULONG_MAX-digit)/10UL ) ) return -1;
    1567         666 :     last = last*10UL + digit;
    1568         666 :     have_last = 1;
    1569         666 :     p++;
    1570         666 :   }
    1571         222 :   if( FD_UNLIKELY( p!=end || (!have_first && !have_last) ) ) return -1;
    1572         198 :   if( FD_UNLIKELY( !object_sz ) ) return -2;
    1573             : 
    1574         183 :   if( !have_first ) {
    1575          39 :     if( FD_UNLIKELY( !last ) ) return -2;
    1576          33 :     *out_range0 = last<object_sz ? object_sz-last : 0UL;
    1577          33 :     *out_range1 = object_sz;
    1578          33 :     return 0;
    1579          39 :   }
    1580             : 
    1581         144 :   if( FD_UNLIKELY( first>=object_sz ) ) return -2;
    1582         117 :   if( FD_UNLIKELY( have_last && last<first ) ) return -2;
    1583         108 :   *out_range0 = first;
    1584         108 :   *out_range1 = have_last && last<object_sz-1UL ? last+1UL : object_sz;
    1585         108 :   return 0;
    1586         117 : }
    1587             : 
    1588             : /* hdr_has_token returns 1 if the given comma separated header value
    1589             :    contains tok (case insensitive). */
    1590             : 
    1591             : static int
    1592             : hdr_has_token( char const * value,
    1593             :                ulong        value_len,
    1594             :                char const * tok,
    1595          24 :                ulong        tok_len ) {
    1596          24 :   char const * p   = value;
    1597          24 :   char const * end = value + value_len;
    1598          33 :   while( p<end ) {
    1599          33 :     while( p<end && ( *p==' ' || *p=='\t' || *p==',' ) ) p++;
    1600          27 :     char const * t0 = p;
    1601         237 :     while( p<end && *p!=',' ) p++;
    1602          27 :     char const * t1 = p;
    1603          27 :     while( t1>t0 && ( t1[-1]==' ' || t1[-1]=='\t' ) ) t1--;
    1604          27 :     if( (ulong)( t1-t0 )==tok_len && !strncasecmp( t0, tok, tok_len ) ) return 1;
    1605          27 :   }
    1606           6 :   return 0;
    1607          24 : }
    1608             : 
    1609             : /* handle_peek handles newly received TCP data.  peek_len bytes of
    1610             :    unconsumed request head sit at conn->req_len in the iobuf. */
    1611             : 
    1612             : static void
    1613             : handle_peek( fd_snapsv_t *       ctx,
    1614             :              fd_stem_context_t * stem,
    1615             :              uint                conn_idx,
    1616             :              int                 res,
    1617         222 :              long                now ) {
    1618         222 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1619         222 :   if( FD_UNLIKELY( res<=0 ) ) {
    1620          39 :     if( FD_UNLIKELY( res<0 ) ) {
    1621          30 :       FD_IP6_ADDR_CSTR( addr_cstr, &conn->peer_ip );
    1622          30 :       FD_LOG_INFO(( "snapshot download peer %s:%u: receive error (%i-%s)",
    1623          30 :                     addr_cstr, conn->peer_port, -res, fd_io_strerror( -res ) ));
    1624          30 :     }
    1625          39 :     conn_close( ctx, stem, conn_idx, now );
    1626          39 :     return;
    1627          39 :   }
    1628         183 :   uint peek_len = (uint)res;
    1629             : 
    1630         183 :   char const * method;  ulong method_len;
    1631         183 :   char const * path;    ulong path_len;
    1632         183 :   int minor_version;
    1633         183 :   struct phr_header headers[ REQ_HEADER_MAX ];
    1634         183 :   ulong header_cnt = REQ_HEADER_MAX;
    1635         183 :   int parsed = phr_parse_request(
    1636         183 :       (char const *)conn_iobuf( ctx, conn ),
    1637         183 :       conn->req.len + peek_len,
    1638         183 :       &method, &method_len,
    1639         183 :       &path,   &path_len,
    1640         183 :       &minor_version,
    1641         183 :       headers, &header_cnt,
    1642         183 :       conn->req.len /* slowloris defense */
    1643         183 :   );
    1644             : 
    1645         183 :   if( FD_UNLIKELY( parsed==-2 ) ) { /* request head incomplete */
    1646          18 :     if( FD_UNLIKELY( conn->req.len+peek_len >= ctx->iobuf_sz ) ) {
    1647           0 :       FD_LOG_DEBUG(( "conn %u: request head exceeds %u bytes", conn_idx, ctx->iobuf_sz ));
    1648           0 :       conn_close( ctx, stem, conn_idx, now );
    1649           0 :       return;
    1650           0 :     }
    1651             :     /* avoid copying the same data again */
    1652          18 :     FD_MCNT_INC( SNAPSV, HTTP_RX_BYTES, peek_len );
    1653          18 :     prep_consume( ctx, conn_idx, peek_len, UDATA_OP_CONSUME_FRAG );
    1654          18 :     conn->state        = CONN_STATE_REQ_SKIP;
    1655          18 :     conn->req.skip_len = peek_len;
    1656          18 :     return;
    1657          18 :   }
    1658             : 
    1659         165 :   if( FD_UNLIKELY( parsed<0 ) ) { /* malformed request head */
    1660           0 :     FD_LOG_DEBUG(( "conn %u: malformed request", conn_idx ));
    1661           0 :     FD_MCNT_INC( SNAPSV, HTTP_RX_BYTES, peek_len );
    1662           0 :     conn_close( ctx, stem, conn_idx, now );
    1663           0 :     return;
    1664           0 :   }
    1665             : 
    1666             :   /* valid request at this point */
    1667             : 
    1668         165 :   FD_CHECK_ERR( (uint)parsed > conn->req.len, "request head shrank" );
    1669         165 :   FD_MCNT_INC( SNAPSV, HTTP_RX_BYTES, (uint)parsed - conn->req.len );
    1670         165 :   prep_consume( ctx, conn_idx, (uint)parsed - conn->req.len, UDATA_OP_CONSUME_TAIL );
    1671         165 :   conn->state       = CONN_STATE_REQ_DONE;
    1672         165 :   conn->req.len     = (uint)parsed - conn->req.len;
    1673         165 :   conn->req.req_seq = ctx->req_seq++;
    1674             : 
    1675             :   /* must not do any work until CONSUME_TAIL completes,
    1676             :      so remember what to do for when that happens */
    1677         165 :   snap_key_t query;
    1678         165 :   uchar      query_hash[ 32 ];
    1679         165 :   int        query_is_zstd;
    1680         165 :   if( method_len==4UL && !memcmp( method, "HEAD", method_len ) ) {
    1681          12 :     conn->req.head = 1;
    1682         153 :   } else if( method_len==3UL && !memcmp( method, "GET", method_len ) ) {
    1683         153 :     conn->req.head = 0;
    1684         153 :   } else {
    1685           0 :     conn->state      = CONN_STATE_RES_WRITE_ERR;
    1686           0 :     conn->res.status = 500;
    1687           0 :     conn->sick       = 1;
    1688           0 :     return;
    1689           0 :   }
    1690             : 
    1691             :   /* parse request headers */
    1692             : 
    1693         165 : #define HDR_RANGE (1U<<0)
    1694         165 : #define HDR_CLEN  (1U<<1)
    1695         165 : #define HDR_CONN  (1U<<2)
    1696             : 
    1697         165 :   char const * range     = NULL;
    1698         165 :   ulong        range_len = 0UL;
    1699         165 :   int          keepalive = minor_version>=1; /* HTTP/1.0 defaults to close */
    1700         165 :   int          bad_req   = 0;  /* reject request with a 400? */
    1701         165 :   uint         seen      = 0U; /* set of HDR_* seen, to detect duplicates */
    1702         246 :   for( ulong i=0UL; i<header_cnt; i++ ) {
    1703          81 :     char const * name      = headers[ i ].name;
    1704          81 :     ulong        name_len  = headers[ i ].name_len;
    1705          81 :     char const * value     = headers[ i ].value;
    1706          81 :     ulong        value_len = headers[ i ].value_len;
    1707          81 :     if( FD_UNLIKELY( !name_len ) ) { /* obs-fold continuation line */
    1708           3 :       bad_req = 1;
    1709           3 :       continue;
    1710           3 :     }
    1711          78 :     if( name_len==5UL && !strncasecmp( name, "range", 5UL ) ) {
    1712          39 :       bad_req  |= !!( seen & HDR_RANGE );
    1713          39 :       seen     |= HDR_RANGE;
    1714          39 :       range     = value;
    1715          39 :       range_len = value_len;
    1716          39 :     } else if( name_len==14UL && !strncasecmp( name, "content-length", 14UL ) ) {
    1717          15 :       bad_req |= !!( seen & HDR_CLEN );
    1718          15 :       seen    |= HDR_CLEN;
    1719          15 :       bad_req |= !( value_len==1UL && value[ 0 ]=='0' ); /* request bodies are not supported */
    1720          24 :     } else if( name_len==17UL && !strncasecmp( name, "transfer-encoding", 17UL ) ) {
    1721           3 :       bad_req = 1; /* chunked bodies are not supported */
    1722          21 :     } else if( name_len==10UL && !strncasecmp( name, "connection", 10UL ) ) {
    1723          18 :       bad_req |= !!( seen & HDR_CONN );
    1724          18 :       seen    |= HDR_CONN;
    1725          18 :       if(      hdr_has_token( value, value_len, "close",      5UL  ) ) keepalive = 0;
    1726           6 :       else if( hdr_has_token( value, value_len, "keep-alive", 10UL ) ) keepalive = 1;
    1727          18 :     }
    1728          78 :   }
    1729             : 
    1730         165 : #undef HDR_RANGE
    1731         165 : #undef HDR_CLEN
    1732         165 : #undef HDR_CONN
    1733             : 
    1734         165 :   if( FD_UNLIKELY( !keepalive ) ) conn->sick = 1;
    1735             : 
    1736         165 :   if( FD_UNLIKELY( bad_req ) ) {
    1737          21 :     conn->state      = CONN_STATE_RES_WRITE_ERR;
    1738          21 :     conn->res.status = 400;
    1739          21 :     conn->sick       = 1;
    1740          21 :     return;
    1741          21 :   }
    1742             : 
    1743         144 :   conn->snap.range    = 0U;
    1744         144 :   conn->snap.req_off0 = 0UL;
    1745         144 :   conn->snap.req_off1 = 0UL;
    1746             : 
    1747             :   /* strip leading slashes */
    1748         144 :   if( FD_UNLIKELY( path_len<10    ) ) goto not_found;
    1749         114 :   if( FD_UNLIKELY( path[ 0 ]!='/' ) ) goto not_found;
    1750         114 :   path++; path_len--;
    1751             :   /* skip superfluous leading slashes to help buggy clients */
    1752         114 :   for( ulong i=0UL; path_len && i<15; i++ ) {
    1753         114 :     if( FD_LIKELY( path[ 0 ]!='/' ) ) break;
    1754           0 :     path++; path_len--;
    1755           0 :   }
    1756             : 
    1757         114 :   if( path_len==16 ) {
    1758          21 :     if( !memcmp( path, "snapshot.tar.zst", 16UL ) ||
    1759          21 :         !memcmp( path, "snapshot.tar.bz2", 16UL ) ) {
    1760          21 :       conn->state           = CONN_STATE_RES_REDIRECT;
    1761          21 :       conn->req.incremental = 0;
    1762          21 :       return;
    1763          21 :     }
    1764          93 :   } else if( path_len==28 ) {
    1765          18 :     if( !memcmp( path, "incremental-snapshot.tar.bz2", 28UL ) ||
    1766          18 :         !memcmp( path, "incremental-snapshot.tar.zst", 28UL ) ) {
    1767          18 :       conn->state           = CONN_STATE_RES_REDIRECT;
    1768          18 :       conn->req.incremental = 1;
    1769          18 :       return;
    1770          18 :     }
    1771          18 :   }
    1772             : 
    1773             :   /* found a snapshot? */
    1774          75 :   if( match_snapshot_path( path, path_len, &query, query_hash, &query_is_zstd ) ) {
    1775          72 :     snap_entry_t * entry = snap_map_update( ctx->snap_map, &query );
    1776          72 :     if( FD_UNLIKELY( !entry ) ) goto not_found;
    1777          69 :     if( FD_UNLIKELY( 0!=memcmp( entry->hash, query_hash, 32UL ) ) ) goto not_found;
    1778          66 :     if( FD_UNLIKELY( entry->is_zstd!=query_is_zstd ) ) goto not_found;
    1779          63 :     conn->state           = CONN_STATE_RES_WRITE_HDR;
    1780          63 :     conn->snap.key        = query;
    1781          63 :     conn->snap.file_sz    = entry->sz;
    1782          63 :     conn->snap.range      = 0U;
    1783          63 :     conn->snap.req_off0   = 0UL;
    1784          63 :     conn->snap.req_off1   = entry->sz;
    1785          63 :     conn->snap.req_cur    = 0UL;
    1786          63 :     conn->req.incremental = entry->key.base_slot!=ULONG_MAX;
    1787             : 
    1788          63 :     if( range && !conn->req.head ) {
    1789          27 :       int range_err = parse_range_header( range, range_len, entry->sz, &conn->snap.req_off0, &conn->snap.req_off1 );
    1790          27 :       if( FD_UNLIKELY( range_err ) ) {
    1791           9 :         conn->state      = CONN_STATE_RES_WRITE_ERR;
    1792           9 :         conn->res.status = (uint)( range_err==-2 ? 416 : 400 );
    1793          18 :       } else {
    1794          18 :         conn->snap.req_cur = conn->snap.req_off0;
    1795          18 :         conn->snap.range   = 1U;
    1796          18 :       }
    1797          27 :     }
    1798          63 :     return;
    1799          66 :   }
    1800             : 
    1801          42 : not_found:
    1802          42 :   conn->state      = CONN_STATE_RES_WRITE_ERR;
    1803          42 :   conn->res.status = 404;
    1804             : 
    1805             :   /* next step happens at handle_consume_tail_comp */
    1806          42 : }
    1807             : 
    1808             : /* handle_consume_frag_comp handles the completion of a CONSUME_FRAG op. */
    1809             : 
    1810             : static void
    1811             : handle_consume_frag_comp( fd_snapsv_t *       ctx,
    1812             :                           fd_stem_context_t * stem,
    1813             :                           uint                conn_idx,
    1814             :                           int                 res,
    1815          18 :                           long                now ) {
    1816          18 :   if( FD_UNLIKELY( res<=0 ) ) {
    1817           0 :     if( FD_UNLIKELY( res<0 ) ) {
    1818           0 :       FD_LOG_DEBUG(( "conn %u: recv() failed (%i-%s)", conn_idx, -res, fd_io_strerror( -res ) ));
    1819           0 :     }
    1820           0 :     conn_close( ctx, stem, conn_idx, now );
    1821           0 :     return;
    1822           0 :   }
    1823          18 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1824          18 :   FD_CHECK_ERR( conn->state==CONN_STATE_REQ_SKIP, "conn state confusion" );
    1825          18 :   if( FD_UNLIKELY( (uint)res!=conn->req.skip_len ) ) {
    1826          15 :     FD_LOG_DEBUG(( "conn %u: consumed %i bytes, requested %u", conn_idx, res, conn->req.skip_len ));
    1827          15 :     conn_close( ctx, stem, conn_idx, now );
    1828          15 :     return;
    1829          15 :   }
    1830           3 :   conn->req.len += conn->req.skip_len;
    1831           3 :   conn->state = CONN_STATE_REQ_PEEK;
    1832           3 :   prep_peek( ctx, conn_idx );
    1833           3 : }
    1834             : 
    1835             : /* handle_consume_tail_comp handles the completion of a CONSUME_TAIL op. */
    1836             : 
    1837             : static void
    1838             : handle_consume_tail_comp( fd_snapsv_t *       ctx,
    1839             :                           fd_stem_context_t * stem,
    1840             :                           uint                conn_idx,
    1841             :                           int                 res,
    1842         165 :                           long                now ) {
    1843         165 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1844         165 :   if( FD_UNLIKELY( res!=(int)conn->req.len ) ) {
    1845           0 :     conn_close( ctx, stem, conn_idx, now );
    1846           0 :     return;
    1847           0 :   }
    1848         165 :   conn->req.len = 0;
    1849         165 :   iobuf_free( ctx, &conn->iobuf_idx );
    1850         165 :   switch( ctx->conn0[ conn_idx ].state ) {
    1851          72 :   case CONN_STATE_RES_WRITE_ERR:
    1852          72 :     serve_http_err( ctx, conn_idx );
    1853          72 :     break;
    1854          54 :   case CONN_STATE_RES_WRITE_HDR:
    1855          54 :     serve_snap_res_hdr( ctx, stem, conn_idx, now );
    1856          54 :     break;
    1857          39 :   case CONN_STATE_RES_REDIRECT:
    1858          39 :     serve_redirect( ctx, conn_idx );
    1859          39 :     break;
    1860           0 :   default:
    1861           0 :     FD_LOG_CRIT(( "conn %u: state confusion", conn_idx ));
    1862         165 :   }
    1863         165 :   FD_MCNT_INC( SNAPSV, HTTP_REQUEST_SERVED, 1UL );
    1864         165 : }
    1865             : 
    1866             : /* futex_prep asks the kernel to send a CQE for when a new
    1867             :    snapmk_out frag becomes available. */
    1868             : 
    1869             : static int
    1870             : futex_prep( fd_snapsv_t * ctx,
    1871       14664 :             ulong         in_idx ) {
    1872       14664 :   ulong seq_prod = fd_mcache_seq_query( ctx->in[ in_idx ].seq_prod );
    1873       14664 :   ulong seq_next = fd_seq_inc( ctx->in[ in_idx ].seq_cons, 1UL );
    1874       14664 :   fd_frag_meta_t const * mline = ctx->in[ in_idx ].mcache + fd_mcache_line_idx( seq_next, ctx->in[ in_idx ].depth );
    1875       14664 :   if( FD_UNLIKELY( fd_frag_meta_seq_query( mline )==seq_next ) ) return 0;
    1876             : 
    1877       14664 :   if( FD_LIKELY( ctx->in[ in_idx ].futex_armed ) ) return 1;
    1878       14664 :   fd_io_uring_t * ring = ctx->ring;
    1879       14664 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
    1880       14664 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1881       14664 :   *sqe = (fd_io_uring_sqe_t) {
    1882       14664 :     .opcode = FD_IORING_OP_FUTEX_WAIT,
    1883       14664 :     .flags  = FD_IOSQE_FIXED_FILE, /* ignored, required for sandbox (hope no kernel ABI breakage) */
    1884       14664 :     .fd     = FUTEX2_SIZE_U32,
    1885       14664 :     .addr   = (ulong)ctx->in[ in_idx ].seq_prod,
    1886       14664 :     .off    = (ulong)(uint)seq_prod, /* value to compare against */
    1887       14664 :     .addr3  = FUTEX_BITSET_MATCH_ANY
    1888       14664 :   };
    1889       14664 :   snapsv_udata_t udata = { .op = UDATA_OP_FUTEX, .conn_idx = (uint)in_idx };
    1890       14664 :   sqe->user_data = udata.user_data;
    1891       14664 :   ctx->in[ in_idx ].futex_armed = 1;
    1892       14664 :   return 1;
    1893       14664 : }
    1894             : 
    1895             : /* futex_comp is called when a snapmk_out frag was detected.  Such
    1896             :    frags are also polled by the event loop, but this is required to wake
    1897             :    up fast from an io_uring_enter sleep. */
    1898             : 
    1899             : static void
    1900             : futex_comp( fd_snapsv_t * ctx,
    1901             :             uint          in_idx,
    1902       14577 :             int           res ) {
    1903       14577 :   FD_CHECK_CRIT( in_idx<IN_LINK_MAX, "io_uring completion has invalid in index" );
    1904       14577 :   ctx->in[ in_idx ].futex_armed = 0;
    1905       14577 :   if( FD_UNLIKELY( res<0 && res!=-EAGAIN && res!=-ECANCELED && res!=-EINTR ) ) {
    1906           0 :     FD_LOG_ERR(( "IORING_OP_FUTEX_WAIT failed (%i-%s)", -res, fd_io_strerror( -res ) ));
    1907           0 :   }
    1908       14577 : }
    1909             : 
    1910             : /* handle_cqe handles a single io_uring completion. */
    1911             : 
    1912             : static void
    1913             : handle_cqe( fd_snapsv_t *             ctx,
    1914             :             fd_stem_context_t *       stem,
    1915             :             fd_io_uring_cqe_t const * cqe,
    1916       16251 :             long                      now ) {
    1917       16251 :   snapsv_udata_t udata = { .user_data = cqe->user_data };
    1918       16251 :   uint conn_idx = udata.conn_idx;
    1919       16251 :   switch( udata.op ) {
    1920         162 :   case UDATA_OP_ACCEPT:
    1921         162 :     handle_accept( ctx, conn_idx, cqe->res, now );
    1922         162 :     break;
    1923         222 :   case UDATA_OP_PEEK:
    1924         222 :     handle_peek( ctx, stem, conn_idx, cqe->res, now );
    1925         222 :     break;
    1926          18 :   case UDATA_OP_CONSUME_FRAG:
    1927          18 :     handle_consume_frag_comp( ctx, stem, conn_idx, cqe->res, now );
    1928          18 :     break;
    1929         165 :   case UDATA_OP_CONSUME_TAIL:
    1930         165 :     handle_consume_tail_comp( ctx, stem, conn_idx, cqe->res, now );
    1931         165 :     break;
    1932          78 :   case UDATA_OP_WRITE_HDR:
    1933          78 :     handle_write_hdr_comp( ctx, stem, conn_idx, cqe->res, now );
    1934          78 :     break;
    1935         246 :   case UDATA_OP_SHOVEL_DISK:
    1936         246 :     shovel_comp_disk( ctx, stem, conn_idx, cqe->res, now );
    1937         246 :     break;
    1938         237 :   case UDATA_OP_SHOVEL_NET:
    1939         237 :     shovel_comp_net( ctx, stem, conn_idx, cqe->res, now );
    1940         237 :     break;
    1941       14577 :   case UDATA_OP_FUTEX:
    1942       14577 :     futex_comp( ctx, conn_idx, cqe->res );
    1943       14577 :     break;
    1944         537 :   case UDATA_OP_TIMEOUT:
    1945         546 :   case UDATA_OP_SHUTDOWN:
    1946         546 :     break;
    1947           0 :   default:
    1948           0 :     FD_LOG_CRIT(( "io_uring completion has invalid user_data 0x%016lx", (ulong)cqe->user_data ));
    1949       16251 :   }
    1950       16251 : }
    1951             : 
    1952             : /* prep_shutdown enqueues a shutdown of a conn's socket, which fails
    1953             :    any pending request receive op. */
    1954             : 
    1955             : static void
    1956             : prep_shutdown( fd_snapsv_t * ctx,
    1957           9 :                uint          conn_idx ) {
    1958           9 :   fd_io_uring_t * ring = ctx->ring;
    1959           9 :   fd_io_uring_sqe_t * sqe = fd_io_uring_get_sqe( ring->sq );
    1960           9 :   FD_CHECK_ERR( sqe, "io_uring submission queue full" );
    1961           9 :   *sqe = (fd_io_uring_sqe_t) {
    1962           9 :     .opcode = FD_IORING_OP_SHUTDOWN,
    1963           9 :     .flags  = FD_IOSQE_FIXED_FILE,
    1964           9 :     .fd     = (int)( ctx->conn0_fd_idx + conn_idx ),
    1965           9 :     .len    = SHUT_RDWR
    1966           9 :   };
    1967           9 :   snapsv_udata_t udata = { .op = UDATA_OP_SHUTDOWN, .conn_idx = conn_idx };
    1968           9 :   sqe->user_data = udata.user_data;
    1969           9 :   ctx->conn0[ conn_idx ].shutdown = 1;
    1970           9 : }
    1971             : 
    1972             : /* idle_sweep shuts down conns that are waiting for a request past the
    1973             :    idle deadline.  The linked timeout on the request peek is not enough
    1974             :    on its own: the kernel can lose a timeout firing while it retries the
    1975             :    receive op. */
    1976             : 
    1977             : static void
    1978             : idle_sweep( fd_snapsv_t * ctx,
    1979             :             uint          conn_idx,
    1980      468288 :             long          now ) {
    1981      468288 :   snapsv_conn_t * conn = &ctx->conn0[ conn_idx ];
    1982      468288 :   if( conn->shutdown ) return;
    1983      468288 :   if( conn->state!=CONN_STATE_REQ_PEEK &&
    1984      468288 :       conn->state!=CONN_STATE_REQ_SKIP &&
    1985      468288 :       conn->state!=CONN_STATE_REQ_DONE ) return;
    1986         396 :   if( now - conn->active_ts < ctx->idle_timeout_nanos ) return;
    1987           9 :   prep_shutdown( ctx, conn_idx );
    1988           9 : }
    1989             : 
    1990             : /* stem_in_update grants flow control credits to an upstream producer. */
    1991             : 
    1992             : static inline void
    1993             : stem_in_update( fd_stem_tile_in_t * in );
    1994             : 
    1995             : /* after_credit performs one io_uring event loop iteration. */
    1996             : 
    1997             : static int
    1998             : after_credit_pre( fd_snapsv_t *       ctx,
    1999             :                   fd_stem_context_t * stem,
    2000             :                   int *               charge_busy,
    2001       14664 :                   long                now ) {
    2002       14664 :   fd_io_uring_t * ring = ctx->ring;
    2003             : 
    2004             :   /* Reap background completions */
    2005       14664 :   uint ready = fd_io_uring_cq_ready( ring->cq );
    2006       14691 :   for( uint i=0U; i<ready; i++ ) {
    2007          27 :     handle_cqe( ctx, stem, fd_io_uring_cq_head( ring->cq ), now );
    2008          27 :     fd_io_uring_cq_advance( ring->cq, 1U );
    2009          27 :     *charge_busy = 1;
    2010          27 :   }
    2011             : 
    2012             :   /* Periodically send a burst update over all GUI tiles.
    2013             :      (This is a deep burst, so the link depth should be about 2x larger
    2014             :      than the conn table size.) */
    2015       14664 :   if( FD_UNLIKELY( now >= ctx->out.next_heartbeat ) ) {
    2016       14634 :     ulong conn_max = ctx->conn_max;
    2017      482922 :     for( uint i=0U; i<conn_max; i++ ) {
    2018      468288 :       snapsv_conn_t * conn = &ctx->conn0[ i ];
    2019      468288 :       if( conn->req.get_snap ) {
    2020         270 :         event_snap( ctx, stem, i, now, 0, 0 );
    2021         270 :       }
    2022      468288 :       idle_sweep( ctx, i, now );
    2023      468288 :     }
    2024       14634 :     ctx->out.next_heartbeat += FD_SNAPSV_SYNC_PERIOD;
    2025       14634 :     if( FD_UNLIKELY( ctx->out.next_heartbeat < now ) ) {
    2026             :       /* more than one period missed, skip ahead timer */
    2027           0 :       ctx->out.next_heartbeat = now + FD_SNAPSV_SYNC_PERIOD;
    2028           0 :     }
    2029       14634 :   }
    2030             : 
    2031             :   /* Prepare for sleep */
    2032       14664 :   int waiting = !!futex_prep( ctx, 0UL );
    2033       14664 :   stem_in_update( &stem->in[ 0 ] );
    2034       14664 :   return waiting;
    2035       14664 : }
    2036             : 
    2037             : static void
    2038             : after_credit_post( fd_snapsv_t *       ctx,
    2039             :                    fd_stem_context_t * stem,
    2040             :                    int *               charge_busy,
    2041       14568 :                    long                now ) {
    2042       14568 :   fd_io_uring_t * ring = ctx->ring;
    2043             : 
    2044       14568 :   if( FD_UNLIKELY( fd_io_uring_sq_dropped( ring->sq ) ) ) {
    2045           0 :     FD_LOG_ERR(( "io_uring submission queue dropped entries" ));
    2046           0 :   }
    2047       14568 :   if( FD_UNLIKELY( fd_io_uring_cq_overflow( ring->cq ) ) ) {
    2048           0 :     FD_LOG_ERR(( "io_uring completion queue overflowed" ));
    2049           0 :   }
    2050             : 
    2051             :   /* Reap deferred/immediate completions */
    2052       14568 :   uint ready = fd_io_uring_cq_ready( ring->cq );
    2053       30792 :   for( uint i=0U; i<ready; i++ ) {
    2054       16224 :     handle_cqe( ctx, stem, fd_io_uring_cq_head( ring->cq ), now );
    2055       16224 :     fd_io_uring_cq_advance( ring->cq, 1U );
    2056       16224 :     *charge_busy = 1;
    2057       16224 :   }
    2058       14568 : }
    2059             : 
    2060             : static void
    2061             : after_credit( fd_snapsv_t *       ctx,
    2062             :               fd_stem_context_t * stem,
    2063             :               int *               opt_poll_in,
    2064           0 :               int *               charge_busy ) {
    2065           0 :   (void)opt_poll_in;
    2066             : 
    2067           0 :   long now = fd_clock_tile_now( ctx->clock );
    2068           0 :   int waiting = after_credit_pre( ctx, stem, charge_busy, now );
    2069             : 
    2070             :   /* Dispatch io_uring work, poll completions, and do a bounded sleep */
    2071           0 :   fd_io_uring_t * ring = ctx->ring;
    2072           0 :   uint tail = ring->sq->sqe_tail;
    2073           0 :   atomic_store_explicit( ring->sq->ktail, tail, memory_order_release );
    2074           0 :   uint head = atomic_load_explicit( ring->sq->khead, memory_order_relaxed );
    2075           0 :   ring->sq->sqe_head = head;
    2076           0 :   uint to_submit = tail - head;
    2077           0 :   FD_STATIC_ASSERT( FD_SNAPSV_WAKE_PERIOD<(long)1e9, bounds );
    2078           0 :   fd_kernel_timespec_t timeout = { .tv_nsec = FD_SNAPSV_WAKE_PERIOD };
    2079           0 :   fd_io_uring_getevents_arg_t enter_arg = { .ts = (ulong)&timeout };
    2080           0 :   int submitted = fd_io_uring_enter(
    2081           0 :       ring->ioring_fd, to_submit, !!waiting,
    2082           0 :       FD_IORING_ENTER_GETEVENTS | FD_IORING_ENTER_EXT_ARG,
    2083           0 :       &enter_arg, sizeof(enter_arg)
    2084           0 :   );
    2085           0 :   now = fd_clock_tile_now( ctx->clock );
    2086           0 :   if( FD_UNLIKELY( submitted<0 ) ) {
    2087           0 :     if( FD_LIKELY( errno==ETIME ) ) {
    2088           0 :       submitted = 0; /* timeout, do housekeeping */
    2089           0 :     } else if( FD_LIKELY( errno==EINTR ) ) {
    2090           0 :       *charge_busy = !!submitted;
    2091           0 :       return;
    2092           0 :     } else {
    2093           0 :       FD_LOG_ERR(( "io_uring_enter failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    2094           0 :     }
    2095           0 :   }
    2096             : 
    2097           0 :   after_credit_post( ctx, stem, charge_busy, now );
    2098           0 :   if( waiting ) *charge_busy = 0; /* do not count sleep time as busy */
    2099           0 : }
    2100             : 
    2101             : /* snap_open registers metadata from snapmk for the given pre-opened
    2102             :    snapshot pool file. */
    2103             : 
    2104             : static void
    2105             : snap_open( fd_snapsv_t * ctx,
    2106             :            ulong         slot,
    2107             :            ulong         base_slot,
    2108             :            ulong         pool_idx,
    2109             :            ulong         sz,
    2110         147 :            char const *  name ) {
    2111         147 :   FD_CHECK_ERR( ctx->snap_cnt_full + ctx->snap_cnt_incr < ctx->snap_max, "too many snapshot files (snapmk_out desync?)" );
    2112         147 :   FD_CHECK_CRIT( pool_idx < ctx->snap_max, "invalid snapshot pool index" );
    2113             : 
    2114         147 :   ulong parsed_full_slot;
    2115         147 :   ulong parsed_incremental_slot;
    2116         147 :   uchar hash[ 32 ];
    2117         147 :   int   is_zstd;
    2118         147 :   FD_CHECK_ERR( !fd_ssarchive_parse_filename( name, &parsed_full_slot, &parsed_incremental_slot, hash, &is_zstd ),
    2119         147 :                 "invalid snapshot filename" );
    2120             : 
    2121         147 :   snap_entry_t * entry = snap_map_insert( ctx->snap_map, &(snap_key_t){slot, base_slot} );
    2122         147 :   FD_CHECK_ERR( !!entry, "snap_map_insert failed (snapmk_out desync?)" );
    2123             : 
    2124         147 :   memcpy( entry->hash, hash, sizeof(entry->hash) );
    2125         147 :   entry->sz     = sz;
    2126         147 :   entry->fd     = FD_SNAP_RO_FD( pool_idx );
    2127         147 :   entry->locked = 0;
    2128         147 :   entry->is_zstd = !!is_zstd;
    2129         147 :   ctx->newest_incr = NULL;
    2130         147 :   if( base_slot==ULONG_MAX ) {
    2131         120 :     ctx->snap_cnt_full++;
    2132         120 :     ctx->newest_full = NULL;
    2133         120 :   } else {
    2134          27 :     ctx->snap_cnt_incr++;
    2135          27 :   }
    2136         147 : }
    2137             : 
    2138             : /* snap_lock tries to acquire a read-lock to a snapshot file handle.
    2139             :    Returns 1 on success, 0 on failure (currently write locked).
    2140             :    Snapshots deliberately remain locked until the snapshot is about to
    2141             :    be deleted to prevent F_SETLK churn. */
    2142             : 
    2143             : FD_FN_UNUSED static int
    2144          54 : snap_lock( snap_entry_t * entry ) {
    2145          54 :   if( entry->locked ) return 1; /* already locked */
    2146             : #ifndef FD_TILE_TEST
    2147           0 :   struct flock lock = {
    2148           0 :     .l_type   = F_RDLCK,
    2149           0 :     .l_whence = SEEK_SET
    2150           0 :   };
    2151           0 :   if( FD_UNLIKELY( fcntl( entry->fd, F_SETLK, &lock ) ) ) {
    2152           0 :     if( errno==EAGAIN || errno==EACCES ) return 0; /* write locked */
    2153           0 :     FD_LOG_ERR(( "fcntl(F_RDLCK, %lu, %lu) failed (%i-%s)", entry->key.slot, entry->key.base_slot, errno, fd_io_strerror( errno ) ));
    2154           0 :   }
    2155           0 : #endif
    2156          54 :   entry->locked = 1;
    2157          54 :   return 1;
    2158           0 : }
    2159             : 
    2160             : /* snap_close releases a snapshot file handle. */
    2161             : 
    2162             : static void
    2163             : snap_close( fd_snapsv_t * ctx,
    2164             :             ulong         slot,
    2165          60 :             ulong         base_slot ) {
    2166          60 :   snap_entry_t * entry = snap_map_update( ctx->snap_map, &(snap_key_t){slot, base_slot} );
    2167          60 :   if( FD_UNLIKELY( !entry ) ) return; /* ignore */
    2168             : 
    2169          51 :   if( entry->locked ) {
    2170             : #ifndef FD_TILE_TEST
    2171             :     struct flock lock = {
    2172             :       .l_type   = F_UNLCK,
    2173             :       .l_whence = SEEK_SET
    2174             :     };
    2175           0 :     if( FD_UNLIKELY( fcntl( entry->fd, F_SETLK, &lock ) ) ) {
    2176           0 :       FD_LOG_ERR(( "fcntl(F_UNLCK, %lu, %lu) failed (%i-%s)", slot, base_slot, errno, fd_io_strerror( errno ) ));
    2177           0 :     }
    2178           0 : #endif
    2179           0 :     entry->locked = 0;
    2180           0 :   }
    2181           0 :   entry->fd = -1;
    2182             : 
    2183           0 :   snap_map_remove( ctx->snap_map, entry );
    2184           0 :   ctx->newest_full = ctx->newest_incr = NULL;
    2185          51 :   if( base_slot==ULONG_MAX ) {
    2186          36 :     FD_CHECK_CRIT( ctx->snap_cnt_full, "full snapshot count underflow" );
    2187          36 :     ctx->snap_cnt_full--;
    2188          36 :   } else {
    2189          15 :     FD_CHECK_CRIT( ctx->snap_cnt_incr, "incremental snapshot count underflow" );
    2190          15 :     ctx->snap_cnt_incr--;
    2191          15 :   }
    2192           0 : }
    2193             : 
    2194             : /* msg_snapmk is called for every snapmk_out frag. */
    2195             : 
    2196             : static void
    2197             : msg_snapmk( fd_snapsv_t *           ctx,
    2198             :             ulong                   msg_type, /* sig */
    2199             :             fd_snapmk_msg_t const * msg,
    2200         126 :             ulong                   msg_sz ) {
    2201         126 :   switch( msg_type ) {
    2202         108 :   case FD_SNAPMK_MSG_FOUND: {
    2203         108 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_found_t), "ABI mismatch" );
    2204         108 :     snap_open( ctx, msg->found.slot, msg->found.base_slot, msg->found.pool_idx, msg->found.sz, msg->found.name );
    2205         108 :     break;
    2206         108 :   }
    2207           0 :   case FD_SNAPMK_MSG_CREATED: {
    2208           0 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_created_t), "ABI mismatch" );
    2209           0 :     snap_open( ctx, msg->created.slot, msg->created.base_slot, msg->created.pool_idx, msg->created.sz, msg->created.name );
    2210           0 :     break;
    2211           0 :   }
    2212          18 :   case FD_SNAPMK_MSG_DELETED: {
    2213          18 :     FD_CHECK_CRIT( msg_sz==sizeof(fd_snapmk_msg_deleted_t), "ABI mismatch" );
    2214          18 :     snap_close( ctx, msg->deleted.slot, msg->deleted.base_slot );
    2215          18 :     break;
    2216          18 :   }
    2217           0 :   default:
    2218           0 :     break;
    2219         126 :   }
    2220         126 : }
    2221             : 
    2222             : /* returnable_frag is called for every input frag */
    2223             : 
    2224             : static int
    2225             : returnable_frag( fd_snapsv_t *       ctx,
    2226             :                  ulong               in_idx,
    2227             :                  ulong               seq,
    2228             :                  ulong               sig,
    2229             :                  ulong               chunk,
    2230             :                  ulong               sz,
    2231             :                  ulong               ctl,
    2232             :                  ulong               tsorig,
    2233             :                  ulong               tspub,
    2234         126 :                  fd_stem_context_t * stem ) {
    2235         126 :   (void)ctl; (void)tsorig; (void)tspub; (void)stem;
    2236         126 :   ctx->in[ in_idx ].seq_cons = seq;
    2237         126 :   switch( ctx->in_kind[ in_idx ] ) {
    2238         126 :   case IN_KIND_SNAPMK:
    2239         126 :     FD_CHECK_CRIT( chunk >= ctx->in[ in_idx ].chunk0 &&
    2240         126 :                    chunk <= ctx->in[ in_idx ].wmark &&
    2241         126 :                    sz    <= ctx->in[ in_idx ].mtu,
    2242         126 :                    "input frag is out-of-bounds" );
    2243         126 :     fd_snapmk_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
    2244         126 :     msg_snapmk( ctx, sig, msg, sz );
    2245         126 :     return 0; /* ok */
    2246           0 :   default:
    2247           0 :     FD_LOG_CRIT(( "unhandled frag from in_idx=%lu", in_idx ));
    2248         126 :   }
    2249         126 : }
    2250             : 
    2251             : static void
    2252           0 : during_housekeeping( fd_snapsv_t * ctx ) {
    2253           0 :   if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) {
    2254           0 :     fd_clock_tile_recal( ctx->clock );
    2255           0 :   }
    2256           0 : }
    2257             : 
    2258             : static void
    2259           0 : metrics_write( fd_snapsv_t * ctx ) {
    2260           0 :   FD_MGAUGE_SET( SNAPSV, SNAPSHOT_AVAILABLE_FULL,         ctx->snap_cnt_full );
    2261           0 :   FD_MGAUGE_SET( SNAPSV, SNAPSHOT_AVAILABLE_INCREMENTAL,  ctx->snap_cnt_incr );
    2262           0 :   FD_MGAUGE_SET( SNAPSV, CONN_OPEN,                       ctx->conn_cnt      );
    2263           0 : }
    2264             : 
    2265             : /* snapsv uses a custom sleep with io_uring_enter and doesn't currently
    2266             :    support parking.  TODO: Fix and align to fd_stem in future. */
    2267             : #define STEM_NEVER_PARK 1
    2268           0 : #define STEM_BURST 1UL
    2269           0 : #define STEM_LAZY  ((long)1e6) /* 1 ms */
    2270           0 : #define STEM_CALLBACK_CONTEXT_TYPE        fd_snapsv_t
    2271           0 : #define STEM_CALLBACK_CONTEXT_ALIGN       alignof(fd_snapsv_t)
    2272           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    2273           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    2274           0 : #define STEM_CALLBACK_RETURNABLE_FRAG     returnable_frag
    2275           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    2276             : #include "../../disco/stem/fd_stem.c"
    2277             : 
    2278             : #ifndef FD_TILE_TEST
    2279             : fd_topo_run_tile_t fd_tile_snapsv = {
    2280             :   .name                     = "snapsv",
    2281             :   .rlimit_file_cnt_fn       = rlimit_file_cnt,
    2282             :   .populate_allowed_fds     = populate_allowed_fds,
    2283             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    2284             :   .scratch_align            = scratch_align,
    2285             :   .scratch_footprint        = scratch_footprint,
    2286             :   .privileged_init          = privileged_init,
    2287             :   .unprivileged_init        = unprivileged_init,
    2288             :   .run                      = stem_run,
    2289             :   .rlimit_nproc             = 1024UL,
    2290             : };
    2291             : #endif

Generated by: LCOV version 1.14