myxce/src/lib/request.c
2026-07-23 12:35:10 +08:00

2134 lines
63 KiB
C

#include <errno.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "xce/errnox.h" /* E_XCE_NOT_READY, E_XCE_STANDBY */
#include <openssl/ssl.h>
#include "mylock.h"
#include "netio.h"
#include "request.h"
#include "typemap.h"
#include "utils.h"
#include "xce/types.h"
#define REQUEST_FROM(req) \
((req)->from == NULL) \
? "Unknown" \
: (((req)->from->name == NULL) ? "Unknown" : (req->from->name))
int xce_encode(xce_request_t* req, io_packet_t* pkt)
{
struct capn c;
struct Request rq;
struct capn_segment* cs;
Request_ptr rp;
int rc;
TRACE("%s request op %d seq %ld from %s", __func__, req->opcode, req->seq,
REQUEST_FROM(req));
capn_init_malloc(&c);
cs = capn_root(&c).seg;
encode_Request(cs, &rq, req);
cs = capn_root(&c).seg;
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
pkt->sz = capn_size(&c);
rc = alloc_packet(pkt);
if (rc < 0) {
capn_free(&c);
return -errno;
}
capn_write_mem(&c, (uint8_t*)pkt->data, pkt->sz, 0);
capn_free(&c);
return 1;
}
/* extract_data_bytes: copy a capnp Data (List(UInt8)) field into a fresh
* malloc'd buffer. Returns NULL (*out_len = 0) for absent/empty fields or
* allocation failure. Used by xce_decode() to pre-extract raw payload
* bytes before capn_free() destroys the segment. */
static uint8_t *extract_data_bytes(capn_data dp, size_t *out_len)
{
*out_len = 0;
capn_list8 l;
l.p = dp.p;
if (l.p.type == CAPN_NULL)
return NULL;
int len = capn_len(l);
if (len <= 0)
return NULL;
uint8_t *buf = (uint8_t *)malloc((size_t)len);
if (!buf)
return NULL;
if (capn_getv8(l, 0, buf, len) != len) {
free(buf);
return NULL;
}
*out_len = (size_t)len;
return buf;
}
int xce_decode(io_packet_t* pkt, xce_request_t* req)
{
struct capn c;
struct Request rq;
Request_ptr rp;
capn_init_mem(&c, (const uint8_t*)pkt->data, pkt->sz, 0);
rp.p = capn_getp(capn_root(&c), 0, 1);
read_Request(&rq, rp);
decode_Request(req, &rq);
/* OPCode_upgradeConfig carries sha256 and bytes as List(UInt8) on the wire.
* decode_UpgradeConfigRequest() intentionally leaves raw_sha256/raw_bytes as
* NULL (the project's Data union cannot carry raw bytes via decode_XceData_ptr).
* Extract them here using UpgradeConfigRequest_get_* + capn_getv8 BEFORE
* capn_free() destroys the capnp segment. */
if (rq.opcode == OPCode_upgradeConfig
&& req->body_which == Request_body_upgradeconfigrequestmsg
&& req->body.upgradeconfigrequestmsg != NULL) {
upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
UpgradeConfigRequest_ptr u_ptr = rq.body.upgradeconfigrequestmsg;
/* sha256 */
{
capn_data dp = UpgradeConfigRequest_get_sha256(u_ptr);
capn_list8 sha_list;
sha_list.p = dp.p;
if (sha_list.p.type != CAPN_NULL) {
int slen = capn_len(sha_list);
if (slen > 0) {
uint8_t *sbuf = (uint8_t *)malloc((size_t)slen);
if (sbuf && capn_getv8(sha_list, 0, sbuf, slen) == slen) {
ucr->raw_sha256 = sbuf;
} else {
free(sbuf);
}
}
}
}
/* bytes */
{
capn_data dp = UpgradeConfigRequest_get_bytes(u_ptr);
capn_list8 bytes_list;
bytes_list.p = dp.p;
if (bytes_list.p.type != CAPN_NULL) {
int blen = capn_len(bytes_list);
if (blen > 0) {
uint8_t *bbuf = (uint8_t *)malloc((size_t)blen);
if (bbuf && capn_getv8(bytes_list, 0, bbuf, blen) == blen) {
ucr->raw_bytes = bbuf;
ucr->raw_bytes_len = (size_t)blen;
} else {
free(bbuf);
}
}
}
}
}
/* OPCode_replConfigPush: same List(UInt8) issue — pre-extract raw bytes. */
if (rq.opcode == OPCode_replConfigPush
&& req->body_which == Request_body_replconfigpushrequestmsg
&& req->body.replconfigpushrequestmsg != NULL) {
repl_config_push_req_t *rcpr = req->body.replconfigpushrequestmsg;
ReplConfigPushRequest_ptr r_ptr = rq.body.replconfigpushrequestmsg;
/* sha256 */
{
capn_data dp = ReplConfigPushRequest_get_sha256(r_ptr);
capn_list8 sha_list; sha_list.p = dp.p;
if (sha_list.p.type != CAPN_NULL && capn_len(sha_list) > 0) {
int slen = capn_len(sha_list);
uint8_t *sbuf = (uint8_t *)malloc((size_t)slen);
if (sbuf && capn_getv8(sha_list, 0, sbuf, slen) == slen)
rcpr->raw_sha256 = sbuf;
else
free(sbuf);
}
}
/* bytes */
{
capn_data dp = ReplConfigPushRequest_get_bytes(r_ptr);
capn_list8 bytes_list; bytes_list.p = dp.p;
if (bytes_list.p.type != CAPN_NULL && capn_len(bytes_list) > 0) {
int blen = capn_len(bytes_list);
uint8_t *bbuf = (uint8_t *)malloc((size_t)blen);
if (bbuf && capn_getv8(bytes_list, 0, bbuf, blen) == blen) {
rcpr->raw_bytes = bbuf;
rcpr->raw_bytes_len = (size_t)blen;
} else {
free(bbuf);
}
}
}
}
/* OPCode_walCatchupEntry: payload :Data per (sub-)entry — pre-extract
* raw bytes into raw_payload/raw_payload_len (replConfigPush pattern).
* The wire entries list order matches the decoded entries[] order. */
if (rq.opcode == OPCode_walCatchupEntry
&& req->body_which == Request_body_walcatchupentrymsg
&& req->body.walcatchupentrymsg != NULL) {
wal_catchup_entry_t *batch = req->body.walcatchupentrymsg;
WalCatchupEntry_ptr b_ptr = rq.body.walcatchupentrymsg;
/* Top-level payload (single-entry mode) */
batch->raw_payload =
extract_data_bytes(WalCatchupEntry_get_payload(b_ptr),
&batch->raw_payload_len);
/* bug-847: the top-level batch_payloads blob — every sub-entry's
* payload concatenated in ONE Data field (see wal_entry_to_wire /
* xce_wal_batch_encode_raw). A single top-level Data blob does not
* exhibit the per-element cross-segment pointer loss that scattered
* per-sub-entry Data blobs did. */
batch->raw_batch_payloads =
extract_data_bytes(WalCatchupEntry_get_batchPayloads(b_ptr),
&batch->raw_batch_payloads_len);
/* ha-term-safety task 3.2: the top-level container's AppendEntries
* metadata (term, prevLogIndex, prevLogTerm, leaderCommit) is decoded
* into batch->{term,prev_log_index,prev_log_term,leader_commit} by the
* generated decode_WalCatchupEntry (all scalar $C.mapname fields) — only
* the Data payload needs manual extraction (above/below). The standby
* intake (ha_consume_append_entries) reads those struct fields directly. */
/* Batch sub-entries: each carries its own lsn/entryType/term/
* wire_payload_len (scalar, decoded — bug-847 immune) + payload. Slice
* this entry's bytes out of the top-level batch_payloads blob using
* wire_payload_len and a running cumulative offset (entries stream in
* the same order they were concatenated on encode). Falls back to the
* legacy per-element Data field only when no batch_payloads blob was
* sent at all (defensive — every current sender populates it). */
WalCatchupEntry_list wlist = WalCatchupEntry_get_entries(b_ptr);
int wn = (wlist.p.type != CAPN_NULL) ? capn_len(wlist) : 0;
size_t batch_off = 0;
for (int i = 0; i < wn && i < batch->n_entries; i++) {
if (!batch->entries || !batch->entries[i])
continue;
struct WalCatchupEntry we;
get_WalCatchupEntry(&we, wlist, i);
if (batch->raw_batch_payloads != NULL) {
uint32_t plen = batch->entries[i]->wire_payload_len;
if (plen > 0 &&
batch_off + plen <= batch->raw_batch_payloads_len) {
uint8_t *slice = (uint8_t *)malloc(plen);
if (slice) {
memcpy(slice, batch->raw_batch_payloads + batch_off,
plen);
batch->entries[i]->raw_payload = slice;
batch->entries[i]->raw_payload_len = plen;
}
batch_off += plen;
}
} else {
batch->entries[i]->raw_payload =
extract_data_bytes(we.payload,
&batch->entries[i]->raw_payload_len);
}
}
}
/* OPCode_snapshotTransfer: chunk payload :Data — pre-extract raw bytes. */
if (rq.opcode == OPCode_snapshotTransfer
&& req->body_which == Request_body_snapshottransfermsg
&& req->body.snapshottransfermsg != NULL) {
snapshot_transfer_t *chunk = req->body.snapshottransfermsg;
SnapshotTransfer_ptr s_ptr = rq.body.snapshottransfermsg;
chunk->raw_payload =
extract_data_bytes(SnapshotTransfer_get_payload(s_ptr),
&chunk->raw_payload_len);
}
TRACE("%s request op %d seq %ld from %s", __func__, req->opcode, req->seq,
REQUEST_FROM(req));
capn_free(&c);
free_packet(pkt);
FREEUP(pkt);
return 0;
}
void init_reply_by_req(xce_reply_t* reply, xce_request_t* req)
{
/* Zero the whole reply first. Callers declare `xce_reply_t reply;` on the
* stack and only populate the fields they need (status / data_which / data),
* leaving the common trailer fields (redirect_host, redirect_port) untouched.
* The generated encode_RequestReply reads redirect_host unconditionally
* (gated only on != NULL), so a stale stack value there is dereferenced via
* strlen() — a heap-use-after-free when the garbage aliases a freed capnp
* segment. Clearing here makes every reply path safe by construction. */
memset(reply, 0, sizeof(*reply));
reply->seq = req->seq;
reply->opcode = req->opcode;
}
/*
* xce_free_request: free an xce_request_t and ALL owned sub-fields.
*
* The generated free_Request() handles the capnp-decoded fields but does
* not know about raw_sha256/raw_bytes on upgrade_config_req_t (those are
* extra fields populated by xce_decode() before capn_free()). This wrapper
* frees those extra fields first, then calls free_Request().
*
* Use this everywhere instead of calling free_Request() directly.
*/
void xce_free_request(xce_request_t *req)
{
if (req == NULL)
return;
/* Free raw_* fields that the generated free_*Request skips */
if (req->body_which == Request_body_upgradeconfigrequestmsg
&& req->body.upgradeconfigrequestmsg != NULL) {
upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
free(ucr->raw_sha256);
ucr->raw_sha256 = NULL;
free(ucr->raw_bytes);
ucr->raw_bytes = NULL;
ucr->raw_bytes_len = 0;
}
if (req->body_which == Request_body_replconfigpushrequestmsg
&& req->body.replconfigpushrequestmsg != NULL) {
repl_config_push_req_t *rcpr = req->body.replconfigpushrequestmsg;
free(rcpr->raw_sha256);
rcpr->raw_sha256 = NULL;
free(rcpr->raw_bytes);
rcpr->raw_bytes = NULL;
rcpr->raw_bytes_len = 0;
}
if (req->body_which == Request_body_walcatchupentrymsg
&& req->body.walcatchupentrymsg != NULL) {
wal_catchup_entry_t *batch = req->body.walcatchupentrymsg;
free(batch->raw_payload);
batch->raw_payload = NULL;
batch->raw_payload_len = 0;
free(batch->raw_batch_payloads);
batch->raw_batch_payloads = NULL;
batch->raw_batch_payloads_len = 0;
for (int i = 0; i < batch->n_entries; i++) {
if (!batch->entries || !batch->entries[i])
continue;
free(batch->entries[i]->raw_payload);
batch->entries[i]->raw_payload = NULL;
batch->entries[i]->raw_payload_len = 0;
}
}
if (req->body_which == Request_body_snapshottransfermsg
&& req->body.snapshottransfermsg != NULL) {
snapshot_transfer_t *chunk = req->body.snapshottransfermsg;
free(chunk->raw_payload);
chunk->raw_payload = NULL;
chunk->raw_payload_len = 0;
}
free_Request(req);
}
int xce_request(endpoint_t* ep, xce_request_t* req)
{
int rc;
io_packet_t* pkt;
pkt = (io_packet_t*)calloc(1, sizeof(*pkt));
if (pkt == NULL) {
return -ENOMEM;
}
req->seq = endpoint_getseq(ep);
req->from = ep->peer;
rc = xce_encode(req, pkt);
if (rc < 0) {
free_packet(pkt);
free(pkt);
return rc;
}
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
int xce_reply(endpoint_t* ep, xce_peerinfo_t* from, xce_reply_t* reply)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_reply,
.body_which = Request_body_replymsg,
};
/* clang-format on */
int rc;
io_packet_t* pkt;
TRACE("%s seq %lu opcode %d status %d data which %d", __func__, reply->seq,
reply->opcode, reply->status, reply->data_which);
pkt = (io_packet_t*)calloc(1, sizeof(*pkt));
if (pkt == NULL) {
return -ENOMEM;
}
request.from = from;
request.body.replymsg = reply;
request.seq = endpoint_getseq(ep);
rc = xce_encode(&request, pkt);
if (rc < 0) {
free_packet(pkt);
free(pkt);
return rc;
}
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
/*
* xce_replconfigpush_req_raw: send OP_replConfigPush with raw byte arrays.
*
* Bypasses encode_ReplConfigPushRequest_ptr (which maps sha256/bytes through
* xce_data_t — a cluster-data discriminated union that cannot carry raw bytes).
* Instead, constructs the struct Request / struct ReplConfigPushRequest directly
* using the capnp wire-format API (capn_new_list8 + capn_setv8), then calls
* write_Request to serialise.
*/
int xce_replconfigpush_req_raw(endpoint_t *ep, xce_peerinfo_t *from,
uint64_t version_tag,
const uint8_t *sha256, size_t sha256_len,
const uint8_t *config_bytes, size_t config_len)
{
struct capn c;
struct capn_segment *cs;
struct Request rq;
struct ReplConfigPushRequest push_req;
ReplConfigPushRequest_ptr push_ptr;
Request_ptr rp;
io_packet_t *pkt;
int rc;
if (ep == NULL)
return -EINVAL;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&rq, 0, sizeof(rq));
memset(&push_req, 0, sizeof(push_req));
push_req.versionTag = version_tag;
/* sha256: write as a List(UInt8) — capn_list8 and capn_data share {capn_ptr p} */
if (sha256 && sha256_len > 0) {
capn_list8 sha_list = capn_new_list8(cs, (int)sha256_len);
capn_setv8(sha_list, 0, sha256, (int)sha256_len);
push_req.sha256.p = sha_list.p;
}
/* config bytes */
if (config_bytes && config_len > 0) {
capn_list8 bytes_list = capn_new_list8(cs, (int)config_len);
capn_setv8(bytes_list, 0, config_bytes, (int)config_len);
push_req.bytes.p = bytes_list.p;
}
push_ptr = new_ReplConfigPushRequest(cs);
write_ReplConfigPushRequest(&push_req, push_ptr);
rq.opcode = OPCode_replConfigPush;
rq.seq = endpoint_getseq(ep);
rq.body_which = Request_body_replconfigpushrequestmsg;
rq.body.replconfigpushrequestmsg = push_ptr;
encode_PeerInfo_ptr(cs, &rq.from, from);
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
if (!pkt) {
capn_free(&c);
return -ENOMEM;
}
pkt->sz = capn_size(&c);
rc = alloc_packet(pkt);
if (rc < 0) {
free(pkt);
capn_free(&c);
return -errno;
}
capn_write_mem(&c, (uint8_t *)pkt->data, pkt->sz, 0);
capn_free(&c);
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
/* wal_entry_to_wire: fill a wire WalCatchupEntry from a C entry's header
* fields and raw payload bytes. Sub-entry nesting is one level deep —
* elements written here never carry their own entries list. */
static void wal_entry_to_wire(struct capn_segment *cs,
struct WalCatchupEntry *w,
const wal_catchup_entry_t *e)
{
memset(w, 0, sizeof(*w));
w->lsn = e->lsn;
w->entryType = e->entry_type;
/* ha-term-safety task 3.2: carry the entry's term stamp so the standby
* intake persists the LEADER's recorded term (R1.3/R1.4), not a substitute. */
w->term = e->term;
/* ha-term-safety task 3.4: carry the AppendEntries prev tuple + leaderCommit
* PER SUB-ENTRY so a multi-entry catch-up batch is routable through the
* library one entry at a time (each sub-entry's prev tuple differs; the
* top-level container can hold only one). For a single-entry broadcast this
* is redundant with the top-level metadata but harmless. */
w->prevLogIndex = e->prev_log_index;
w->prevLogTerm = e->prev_log_term;
w->leaderCommit = e->leader_commit;
/* bug-1067: carry the streaming leader's CURRENT term per sub-entry (distinct
* from `term` above, the entry's stored term). The standby intake uses this as
* the AppendEntries header term so historical (entry_term < leader_term)
* catch-up entries are not stale-rejected. */
w->leaderCurrentTerm = e->leader_current_term;
/* bug-847: do NOT write this sub-entry's payload as its own per-element
* Data blob — that is exactly the shape that loses its pointer once a
* multi-entry batch crosses a capnp segment boundary. The caller
* (xce_wal_batch_encode_raw) instead carries every sub-entry's bytes
* concatenated in ONE top-level batchPayloads blob; this scalar length
* is all a sub-entry needs to find its slice on decode. */
(void)cs;
w->wirePayloadLen = (uint32_t)e->raw_payload_len;
}
/*
* xce_wal_batch_encode_raw: encode an OPCode_walCatchupEntry batch into pkt.
*
* Bypasses encode_WalCatchupEntry (whose payload field routes through
* xce_data_t — cannot carry raw bytes; same limitation as replConfigPush).
* Builds the Request manually: a top-level WalCatchupEntry container
* (lsn=0, entryType=0) whose entries list holds one element per batch
* entry, each with lsn/entryType and payload written as capnp Data from
* raw_payload/raw_payload_len.
*
* pkt is caller-allocated; on success it holds the serialized message
* (caller owns; enqueue or free with free_packet). Returns 0 or -errno.
*/
int xce_wal_batch_encode_raw(wal_catchup_entry_t **entries, int n_entries,
xce_peerinfo_t *from, uint64_t seq,
io_packet_t *pkt)
{
struct capn c;
struct capn_segment *cs;
struct Request rq;
struct WalCatchupEntry top;
WalCatchupEntry_ptr tp;
Request_ptr rp;
int rc;
/* n_entries == 0 is a valid AppendEntries HEARTBEAT (ha-term-safety task 3.2):
* entries[0] still carries the AppendEntries metadata (prev tuple +
* leader_commit) on the top-level container, with an empty entries list. */
if (pkt == NULL || n_entries < 0 || entries == NULL)
return -EINVAL;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&rq, 0, sizeof(rq));
memset(&top, 0, sizeof(top));
/* ha-term-safety task 3.2: the top-level container carries the AppendEntries
* metadata (prev tuple + leaderCommit) so a 0-entry heartbeat still conveys
* UpdateTerm/logOk inputs. entries[0] is the metadata holder for both the
* heartbeat (n_entries==0) and the single-entry (n_entries==1) AE built by
* repl_send_append_entries. Legacy catch-up batches leave these zero. */
top.term = entries[0]->term;
top.prevLogIndex = entries[0]->prev_log_index;
top.prevLogTerm = entries[0]->prev_log_term;
top.leaderCommit = entries[0]->leader_commit;
/* bug-1067: the AppendEntries header term (leader's CURRENT term), distinct
* from top.term (entries[0]'s stored term). Mirrors the per-entry field. */
top.leaderCurrentTerm = entries[0]->leader_current_term;
/* Batch container: entries list carries the per-entry payload(s) (empty for
* a heartbeat). Each sub-entry carries lsn/entryType/term/wirePayloadLen. */
WalCatchupEntry_list lst = new_WalCatchupEntry_list(cs, n_entries);
for (int i = 0; i < n_entries; i++) {
struct WalCatchupEntry w;
wal_entry_to_wire(cs, &w, entries[i]);
set_WalCatchupEntry(&w, lst, i);
}
top.entries = lst;
/* bug-847: carry every sub-entry's payload concatenated in ONE top-level
* Data blob instead of one Data blob per sub-entry — a single blob does
* not exhibit the cross-segment pointer loss that scattered per-element
* Data blobs do in a multi-entry batch. Decode slices this back apart
* per entry using each entry's own wirePayloadLen (set above). */
{
size_t total = 0;
for (int i = 0; i < n_entries; i++)
total += entries[i]->raw_payload_len;
if (total > 0) {
uint8_t *concat = (uint8_t *)malloc(total);
if (!concat) {
capn_free(&c);
return -ENOMEM;
}
size_t off = 0;
for (int i = 0; i < n_entries; i++) {
if (entries[i]->raw_payload && entries[i]->raw_payload_len > 0) {
memcpy(concat + off, entries[i]->raw_payload,
entries[i]->raw_payload_len);
off += entries[i]->raw_payload_len;
}
}
capn_list8 pl = capn_new_list8(cs, (int)total);
capn_setv8(pl, 0, concat, (int)total);
top.batchPayloads.p = pl.p;
free(concat);
}
}
tp = new_WalCatchupEntry(cs);
write_WalCatchupEntry(&top, tp);
rq.opcode = OPCode_walCatchupEntry;
rq.seq = seq;
rq.body_which = Request_body_walcatchupentrymsg;
rq.body.walcatchupentrymsg = tp;
encode_PeerInfo_ptr(cs, &rq.from, from);
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
pkt->sz = capn_size(&c);
rc = alloc_packet(pkt);
if (rc < 0) {
capn_free(&c);
return -errno;
}
capn_write_mem(&c, (uint8_t *)pkt->data, pkt->sz, 0);
capn_free(&c);
return 0;
}
int xce_wal_batch_req_raw(endpoint_t *ep, wal_catchup_entry_t **entries,
int n_entries)
{
if (ep == NULL)
return -EINVAL;
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
if (!pkt)
return -ENOMEM;
int rc = xce_wal_batch_encode_raw(entries, n_entries, ep->peer,
endpoint_getseq(ep), pkt);
if (rc < 0) {
free(pkt);
return rc;
}
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
/*
* xce_snapshot_chunk_encode_raw: encode an OPCode_snapshotTransfer chunk
* into pkt. Envelope fields from *chunk; payload bytes written as capnp
* Data from payload/payload_len (the legacy xce_data_t payload field is
* ignored). pkt is caller-allocated. Returns 0 or -errno.
*/
int xce_snapshot_chunk_encode_raw(const snapshot_transfer_t *chunk,
const uint8_t *payload, size_t payload_len,
xce_peerinfo_t *from, uint64_t seq,
io_packet_t *pkt)
{
struct capn c;
struct capn_segment *cs;
struct Request rq;
struct SnapshotTransfer st;
SnapshotTransfer_ptr sp;
Request_ptr rp;
int rc;
if (pkt == NULL || chunk == NULL)
return -EINVAL;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&rq, 0, sizeof(rq));
memset(&st, 0, sizeof(st));
st.snapshotLsn = chunk->snapshot_lsn;
st.chunkIndex = chunk->chunk_index;
st.chunkOffset = chunk->chunk_offset;
st.totalSize = chunk->total_size;
st.isLast = chunk->is_last ? 1 : 0;
/* raft-ha-core-safety task 3.2: checkpoint's term at snapshotLsn, so the
* receiver can reconcile its per-LSN term index at the boundary. */
st.checkpointTerm = chunk->checkpoint_term;
if (payload && payload_len > 0) {
capn_list8 pl = capn_new_list8(cs, (int)payload_len);
capn_setv8(pl, 0, payload, (int)payload_len);
st.payload.p = pl.p;
}
sp = new_SnapshotTransfer(cs);
write_SnapshotTransfer(&st, sp);
rq.opcode = OPCode_snapshotTransfer;
rq.seq = seq;
rq.body_which = Request_body_snapshottransfermsg;
rq.body.snapshottransfermsg = sp;
encode_PeerInfo_ptr(cs, &rq.from, from);
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
pkt->sz = capn_size(&c);
rc = alloc_packet(pkt);
if (rc < 0) {
capn_free(&c);
return -errno;
}
capn_write_mem(&c, (uint8_t *)pkt->data, pkt->sz, 0);
capn_free(&c);
return 0;
}
int xce_snapshot_chunk_req_raw(endpoint_t *ep, const snapshot_transfer_t *chunk,
const uint8_t *payload, size_t payload_len)
{
if (ep == NULL)
return -EINVAL;
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
if (!pkt)
return -ENOMEM;
int rc = xce_snapshot_chunk_encode_raw(chunk, payload, payload_len,
ep->peer, endpoint_getseq(ep), pkt);
if (rc < 0) {
free(pkt);
return rc;
}
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
/*
* xce_replconfig_reply_raw: primary sends OP_replConfig reply with real
* config bytes via an endpoint (uses iomgr queueing).
*
* Bypasses encode_ReplConfigReply_ptr (which routes sha256/bytes through
* xce_data_t — a discriminated cluster-data union that cannot carry raw
* bytes). Constructs Request { opcode=OPCode_reply, body=replymsg }
* with RequestReply { data=replconfigreplymsg } directly via the capnp
* wire-format API (capn_new_list8 + capn_setv8), then enqueues for send.
*/
int xce_replconfig_reply_raw(endpoint_t *ep, xce_peerinfo_t *from,
uint64_t seq, uint64_t tag,
const uint8_t *sha256, size_t sha256_len,
const uint8_t *config, size_t config_len)
{
struct capn c;
struct capn_segment *cs;
struct Request rq;
struct RequestReply rr;
struct ReplConfigReply reply_struct;
ReplConfigReply_ptr reply_ptr;
RequestReply_ptr rr_ptr;
Request_ptr rp;
io_packet_t *pkt;
int rc;
if (ep == NULL)
return -EINVAL;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&rq, 0, sizeof(rq));
memset(&rr, 0, sizeof(rr));
memset(&reply_struct, 0, sizeof(reply_struct));
/* ReplConfigReply fields */
reply_struct.upToDate = 0;
reply_struct.versionTag = tag;
if (sha256 && sha256_len > 0) {
capn_list8 sha_list = capn_new_list8(cs, (int)sha256_len);
capn_setv8(sha_list, 0, sha256, (int)sha256_len);
reply_struct.sha256.p = sha_list.p;
}
if (config && config_len > 0) {
capn_list8 bytes_list = capn_new_list8(cs, (int)config_len);
capn_setv8(bytes_list, 0, config, (int)config_len);
reply_struct.bytes.p = bytes_list.p;
}
reply_ptr = new_ReplConfigReply(cs);
write_ReplConfigReply(&reply_struct, reply_ptr);
/* RequestReply envelope */
rr.seq = seq;
rr.opcode = OPCode_replConfig; /* opcode being replied to */
rr.status = 0;
rr.data_which = RequestReply_data_replconfigreplymsg;
rr.data.replconfigreplymsg = reply_ptr;
rr_ptr = new_RequestReply(cs);
write_RequestReply(&rr, rr_ptr);
/* Outer Request */
rq.opcode = OPCode_reply;
rq.seq = seq;
rq.body_which = Request_body_replymsg;
rq.body.replymsg = rr_ptr;
encode_PeerInfo_ptr(cs, &rq.from, from);
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
if (!pkt) {
capn_free(&c);
return -ENOMEM;
}
pkt->sz = capn_size(&c);
rc = alloc_packet(pkt);
if (rc < 0) {
free(pkt);
capn_free(&c);
return -errno;
}
capn_write_mem(&c, (uint8_t *)pkt->data, pkt->sz, 0);
capn_free(&c);
int enq_rc = endpoint_enq(ep, &(pkt->link));
return (enq_rc < 0) ? enq_rc : 0;
}
/*
* xce_replconfig_req_ssl: standby sends OP_replConfig over a raw SSL
* socket (no iomgr). Used during early-boot config fetch from primary
* before the iomgr is fully wired.
*
* Wire framing matches netio.c: uint64_t size prefix (host byte order)
* followed by `size` bytes of capnp data.
*/
int xce_replconfig_req_ssl(SSL *ssl, uint64_t version_tag)
{
struct capn c;
struct capn_segment *cs;
struct Request rq;
struct ReplConfigRequest req_struct;
ReplConfigRequest_ptr req_ptr;
Request_ptr rp;
uint8_t *buf = NULL;
int64_t blen = 0;
int rc = 0;
if (ssl == NULL)
return -EINVAL;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&rq, 0, sizeof(rq));
memset(&req_struct, 0, sizeof(req_struct));
req_struct.versionTag = version_tag;
req_ptr = new_ReplConfigRequest(cs);
write_ReplConfigRequest(&req_struct, req_ptr);
rq.opcode = OPCode_replConfig;
rq.seq = 0;
rq.body_which = Request_body_replconfigrequestmsg;
rq.body.replconfigrequestmsg = req_ptr;
/* from = NULL (no peerinfo for one-shot SSL fetch) */
encode_PeerInfo_ptr(cs, &rq.from, NULL);
rp = new_Request(cs);
write_Request(&rq, rp);
capn_setp(capn_root(&c), 0, rp.p);
blen = capn_size(&c);
if (blen <= 0) {
capn_free(&c);
return -EIO;
}
buf = (uint8_t *)malloc((size_t)blen);
if (!buf) {
capn_free(&c);
return -ENOMEM;
}
if (capn_write_mem(&c, buf, (size_t)blen, 0) < 0) {
free(buf);
capn_free(&c);
return -EIO;
}
capn_free(&c);
/* Wire framing: 8-byte size header then payload (matches netio.c
* io_fsm_perform: writes &pkt->sz raw, then pkt->data). */
{
uint64_t hdr = (uint64_t)blen;
int nsent = 0;
nsent = SSL_write(ssl, &hdr, (int)sizeof(hdr));
if (nsent != (int)sizeof(hdr)) {
rc = -EIO;
goto out;
}
nsent = SSL_write(ssl, buf, (int)blen);
if (nsent != (int)blen) {
rc = -EIO;
goto out;
}
}
out:
free(buf);
return rc;
}
/* Helper: read exactly `len` bytes from SSL, looping on partial reads. */
static int ssl_read_all(SSL *ssl, void *dst, size_t len)
{
size_t off = 0;
while (off < len) {
int n = SSL_read(ssl, (char *)dst + off, (int)(len - off));
if (n <= 0) {
return -EIO;
}
off += (size_t)n;
}
return 0;
}
/*
* xce_replconfig_reply_ssl_decode: standby reads the primary's
* OP_replConfig reply from a raw SSL socket and extracts up_to_date,
* version_tag, sha256 and bytes.
*
* Bypasses xce_decode (which would route sha256/bytes through
* decode_XceData_ptr → xce_data_t and lose the raw byte content).
* Instead, reads the capnp message directly into capn, walks down
* Request → RequestReply → ReplConfigReply, and pulls sha256/bytes
* via capn_len + capn_getv8 on the raw capn_list8.
*
* Output buffers (*out_sha256 and *out_bytes) are malloc'd and must
* be freed by the caller. Both may be NULL on a fast path
* (up_to_date == 1). Caller is responsible for distinguishing.
*/
int xce_replconfig_reply_ssl_decode(SSL *ssl,
int *out_up_to_date,
uint64_t *out_tag,
uint8_t **out_sha256,
uint8_t **out_bytes,
size_t *out_len)
{
struct capn c;
Request_ptr rp;
struct Request rq;
RequestReply_ptr rr_ptr;
struct RequestReply rr;
ReplConfigReply_ptr reply_ptr;
struct ReplConfigReply reply_struct;
capn_list8 sha_list;
capn_list8 bytes_list;
uint64_t hdr = 0;
uint8_t *buf = NULL;
int rc = 0;
if (ssl == NULL || out_up_to_date == NULL || out_tag == NULL
|| out_sha256 == NULL || out_bytes == NULL || out_len == NULL) {
return -EINVAL;
}
*out_up_to_date = 0;
*out_tag = 0;
*out_sha256 = NULL;
*out_bytes = NULL;
*out_len = 0;
rc = ssl_read_all(ssl, &hdr, sizeof(hdr));
if (rc < 0) {
return rc;
}
if (hdr == 0 || hdr > (uint64_t)(64 * 1024 * 1024)) {
/* 64 MiB sanity ceiling */
return -EMSGSIZE;
}
buf = (uint8_t *)malloc((size_t)hdr);
if (!buf) {
return -ENOMEM;
}
rc = ssl_read_all(ssl, buf, (size_t)hdr);
if (rc < 0) {
free(buf);
return rc;
}
if (capn_init_mem(&c, buf, (size_t)hdr, 0) < 0) {
free(buf);
return -EPROTO;
}
rp.p = capn_getp(capn_root(&c), 0, 1);
read_Request(&rq, rp);
if (rq.body_which != Request_body_replymsg) {
rc = -EPROTO;
goto cleanup;
}
rr_ptr = rq.body.replymsg;
read_RequestReply(&rr, rr_ptr);
if (rr.data_which != RequestReply_data_replconfigreplymsg) {
rc = -EPROTO;
goto cleanup;
}
reply_ptr = rr.data.replconfigreplymsg;
read_ReplConfigReply(&reply_struct, reply_ptr);
*out_up_to_date = (int)reply_struct.upToDate;
*out_tag = reply_struct.versionTag;
/* Pull sha256 and bytes via raw capn_list8 access (NOT decode_XceData
* which would corrupt via xce_data_t). XceData_ptr and capn_list8
* share {capn_ptr p} layout. */
sha_list.p = reply_struct.sha256.p;
bytes_list.p = reply_struct.bytes.p;
if (sha_list.p.type != CAPN_NULL && capn_len(sha_list) > 0) {
int slen = capn_len(sha_list);
uint8_t *sbuf = (uint8_t *)malloc((size_t)slen);
if (!sbuf) {
rc = -ENOMEM;
goto cleanup;
}
if (capn_getv8(sha_list, 0, sbuf, slen) != slen) {
free(sbuf);
rc = -EPROTO;
goto cleanup;
}
*out_sha256 = sbuf;
}
if (bytes_list.p.type != CAPN_NULL && capn_len(bytes_list) > 0) {
int blen = capn_len(bytes_list);
uint8_t *bbuf = (uint8_t *)malloc((size_t)blen);
if (!bbuf) {
free(*out_sha256);
*out_sha256 = NULL;
rc = -ENOMEM;
goto cleanup;
}
if (capn_getv8(bytes_list, 0, bbuf, blen) != blen) {
free(bbuf);
free(*out_sha256);
*out_sha256 = NULL;
rc = -EPROTO;
goto cleanup;
}
*out_bytes = bbuf;
*out_len = (size_t)blen;
}
cleanup:
capn_free(&c);
free(buf);
return rc;
}
/*
* xce_upgrade_config_req_extract: extract sha256 + bytes from a decoded
* OP_upgradeConfig request.
*
* xce_decode() now extracts the raw List(UInt8) bytes from the capnp
* segment before capn_free(), storing malloc'd copies in
* req->body.upgradeconfigrequestmsg->raw_sha256 and ->raw_bytes.
* This function returns shallow copies (pointers into the struct); the
* caller must NOT free them — they are owned by the xce_request_t and
* released by free_Request().
*
* Returns 0 on success, -EINVAL on bad args or missing fields.
*/
int xce_upgrade_config_req_extract(const xce_request_t *req,
uint8_t **out_bytes, size_t *out_len,
uint8_t **out_sha256)
{
if (req == NULL || out_bytes == NULL || out_len == NULL
|| out_sha256 == NULL) {
return -EINVAL;
}
*out_bytes = NULL;
*out_len = 0;
*out_sha256 = NULL;
if (req->body_which != Request_body_upgradeconfigrequestmsg
|| req->body.upgradeconfigrequestmsg == NULL) {
return -EINVAL;
}
const upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
if (ucr->raw_sha256 == NULL || ucr->raw_bytes == NULL
|| ucr->raw_bytes_len == 0) {
return -EINVAL;
}
*out_sha256 = ucr->raw_sha256;
*out_bytes = ucr->raw_bytes;
*out_len = ucr->raw_bytes_len;
return 0;
}
/*
* xce_upgrade_config_req_extract_raw: parse OP_upgradeConfig request
* bytes from a raw on-wire packet and extract sha256 + config bytes.
*
* Use this from handle_upgrade_config() once a path is wired to capture
* the io_packet_t->data buffer before xce_decode() tears it down (see
* the xce_upgrade_config_req_extract() comment for why the decoded
* form is unusable).
*
* Output buffers (*out_bytes, *out_sha256) are malloc'd and owned by
* the caller.
*/
int xce_upgrade_config_req_extract_raw(const uint8_t *pkt_data, size_t pkt_sz,
uint8_t **out_bytes, size_t *out_len,
uint8_t **out_sha256)
{
struct capn c;
Request_ptr rp;
struct Request rq;
UpgradeConfigRequest_ptr u_ptr;
struct UpgradeConfigRequest u_struct;
capn_list8 sha_list;
capn_list8 bytes_list;
int rc = 0;
if (pkt_data == NULL || pkt_sz == 0 || out_bytes == NULL
|| out_len == NULL || out_sha256 == NULL) {
return -EINVAL;
}
*out_bytes = NULL;
*out_len = 0;
*out_sha256 = NULL;
if (capn_init_mem(&c, pkt_data, pkt_sz, 0) < 0) {
return -EPROTO;
}
rp.p = capn_getp(capn_root(&c), 0, 1);
read_Request(&rq, rp);
if (rq.body_which != Request_body_upgradeconfigrequestmsg) {
rc = -EPROTO;
goto cleanup;
}
u_ptr = rq.body.upgradeconfigrequestmsg;
read_UpgradeConfigRequest(&u_struct, u_ptr);
sha_list.p = u_struct.sha256.p;
bytes_list.p = u_struct.bytes.p;
if (sha_list.p.type == CAPN_NULL || capn_len(sha_list) <= 0
|| bytes_list.p.type == CAPN_NULL || capn_len(bytes_list) <= 0) {
rc = -EPROTO;
goto cleanup;
}
{
int slen = capn_len(sha_list);
int blen = capn_len(bytes_list);
uint8_t *sbuf;
uint8_t *bbuf;
sbuf = (uint8_t *)malloc((size_t)slen);
if (!sbuf) {
rc = -ENOMEM;
goto cleanup;
}
if (capn_getv8(sha_list, 0, sbuf, slen) != slen) {
free(sbuf);
rc = -EPROTO;
goto cleanup;
}
bbuf = (uint8_t *)malloc((size_t)blen);
if (!bbuf) {
free(sbuf);
rc = -ENOMEM;
goto cleanup;
}
if (capn_getv8(bytes_list, 0, bbuf, blen) != blen) {
free(sbuf);
free(bbuf);
rc = -EPROTO;
goto cleanup;
}
*out_sha256 = sbuf;
*out_bytes = bbuf;
*out_len = (size_t)blen;
}
cleanup:
capn_free(&c);
return rc;
}
int xce_nop_req(endpoint_t* ep)
{
xce_request_t request
= { .opcode = OPCode_nop, .body_which = Request_body_nomsg };
if (ep == NULL) {
return -EINVAL;
}
return xce_request(ep, &request);
}
/* xce_ha_heartbeat_req: send the HA heartbeat as an OP_nop request carrying a
* HeartbeatMsg body with the primary's committed position. Clone of the nop
* send path plus a heartbeatmsg body. Non-blocking, no reply awaited (the
* standby's StateReport rides the normal nop reply). senderPriority is unused
* (0). Generic xce_nop_req stays body-less. */
int xce_ha_heartbeat_req(endpoint_t* ep, uint64_t term, uint64_t committed_lsn,
uint64_t committed_config_tag)
{
heartbeat_msg_t hb = {
.sender_priority = 0,
.term = term,
.committed_lsn = committed_lsn,
.committed_config_tag = committed_config_tag,
};
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_nop,
.body_which = Request_body_heartbeatmsg,
.body = {
.heartbeatmsg = &hb
}
};
/* clang-format on */
if (ep == NULL) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_hello_req(endpoint_t* ep, char* msg)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_hello,
.body_which = Request_body_hellomsg,
.body = {
.hellomsg = msg
}
};
/* clang-format on */
if ((ep == NULL) || (msg == NULL)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_join_req(endpoint_t* ep, int role, hostattrs_t* attrs)
{
/* clang-format off */
xce_joindata_t joinmsg = {
.role = role
};
xce_request_t request = {
.opcode = OPCode_join,
.body_which = Request_body_joinmsg,
.body = {
.joinmsg = &joinmsg
}
};
if ((role != HostRole_client) &&
(role != HostRole_worker) &&
(role != HostRole_apiserver) &&
(role != HostRole_scheduler)) {
return -EINVAL;
}
/* clang-format on */
if (role == HostRole_worker) {
if (attrs == NULL) {
return -EINVAL;
}
joinmsg.data_which = JoinData_data_attrs;
joinmsg.data.attrs = attrs;
}
return xce_request(ep, &request);
}
int xce_shutdown_req(endpoint_t* ep)
{
int rc;
dataq_t* outq;
xce_request_t request
= { .opcode = OPCode_shutdown, .body_which = Request_body_nomsg };
if (ep == NULL) {
return -EINVAL;
}
rc = xce_request(ep, &request);
if (rc < 0) {
return rc;
}
outq = &(ep->outq);
while (!DATAQ_EMPTY(outq)) {
sleep(1);
}
return 0;
}
int xce_pushconfig_req(endpoint_t* ep, xce_peerinfo_t* from,
xce_configdata_t* config)
{
int rc = 0;
/* clang-format off */
xce_request_t request
= { .opcode = OPCode_push, .body_which = Request_body_configpushmsg };
/* clang-format on */
if ((ep == NULL) || (config == NULL)) {
return -EINVAL;
}
request.body.configpushmsg = config;
rc = xce_request(ep, &request);
return rc;
}
static int xce_push_req(endpoint_t* ep, xce_peerinfo_t* from, xce_data_t* data)
{
int rc = 0;
xce_request_t request
= { .opcode = OPCode_push, .body_which = Request_body_pushmsg };
if ((ep == NULL) || (from == NULL) || (data == NULL)) {
return -EINVAL;
}
request.body.pushmsg = data;
rc = xce_request(ep, &request);
return rc;
}
int xce_push_data_req(endpoint_t* ep, xce_peerinfo_t* from, xce_data_t* data,
xce_check_fn_t checkf, void* checkarg, int pktlimit)
{
int nrows = 0;
int pos = 0;
int rc = 0;
xce_data_t data1;
int checkstatus = 0;
if ((ep == NULL) || (from == NULL) || (data == NULL)) {
return -EINVAL;
}
data1.table = data->table;
switch (data->table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
nrows = data->data.jobs->n_total;
data1.data.jobs = (xce_jobs_data_t*)calloc(1, sizeof(xce_jobs_data_t));
if (data1.data.jobs == NULL) {
rc = -ENOMEM;
}
break;
case XceData_data_schedworkers:
case XceData_data_workers:
nrows = data->data.workers->n_total;
data1.data.workers
= (xce_workers_data_t*)calloc(1, sizeof(xce_workers_data_t));
if (data1.data.workers == NULL) {
rc = -ENOMEM;
}
break;
case XceData_data_resources:
break;
case XceData_data_resvals:
break;
default:
rc = -EINVAL;
break;
}
if (rc < 0) {
return rc;
}
if (pktlimit <= 0) {
pktlimit = nrows;
}
do {
int nc = nrows - pos;
if (nc > pktlimit) {
nc = pktlimit;
}
if (checkf != NULL) {
checkstatus = (*checkf)(checkarg);
if (checkstatus < 0) {
nc = 0;
}
}
switch (data->table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
data1.data.jobs->n_total = data->data.jobs->n_total;
data1.data.jobs->pos = pos;
data1.data.jobs->count = nc;
if (nc == 0) {
data1.data.jobs->jobs = NULL;
} else {
data1.data.jobs->jobs = &(data->data.jobs->jobs[pos]);
}
break;
case XceData_data_schedworkers:
case XceData_data_workers:
data1.data.workers->n_total = data->data.workers->n_total;
data1.data.workers->pos = pos;
data1.data.workers->count = nc;
if (nc == 0) {
data1.data.workers->workers = NULL;
} else {
data1.data.workers->workers
= &(data->data.workers->workers[pos]);
}
break;
case XceData_data_resources:
break;
case XceData_data_resvals:
break;
default:
break;
}
rc = xce_push_req(ep, from, &data1);
if (rc < 0) {
break;
}
pos += nc;
} while ((pos < nrows) && (checkstatus >= 0));
switch (data->table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
FREEUP(data1.data.jobs);
break;
case XceData_data_schedworkers:
case XceData_data_workers:
FREEUP(data1.data.workers);
break;
default:
break;
}
return rc;
}
int xce_get_req(endpoint_t* ep, xce_get_t* get)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_get,
.body_which = Request_body_getmsg,
.body = {
.getmsg = get
}
};
/* clang-format on */
return xce_request(ep, &request);
}
int xce_reply_get(endpoint_t* ep, xce_peerinfo_t* from, xce_reply_t* reply,
xce_check_fn_t checkf, void* checkarg, int pktlimit)
{
int rc = 0;
int nrows = 0;
int pos = 0;
/* clang-format off */
xce_data_t getdata = {
.table = reply->data.getdata->table
};
xce_reply_t reply1 = {
.seq = reply->seq,
.opcode = reply->opcode,
.status = reply->status,
.data_which = reply->data_which,
.data = {
.getdata = &getdata
}
};
/* clang-format on */
int checkstatus = 0;
xce_data_t* data = reply->data.getdata;
if ((ep == NULL) || (from == NULL) || (reply == NULL)) {
return -EINVAL;
}
switch (getdata.table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
nrows = data->data.jobs->n_total;
getdata.data.jobs
= (xce_jobs_data_t*)calloc(1, sizeof(xce_jobs_data_t));
break;
case XceData_data_schedworkers:
case XceData_data_workers:
nrows = data->data.workers->n_total;
getdata.data.workers
= (xce_workers_data_t*)calloc(1, sizeof(xce_workers_data_t));
break;
case XceData_data_resources:
nrows = data->data.resources->n_total;
getdata.data.resources
= (xce_resource_data_t*)calloc(1, sizeof(xce_resource_data_t));
break;
case XceData_data_resvals:
nrows = data->data.resvals->n_total;
getdata.data.resvals
= (xce_resval_data_t*)calloc(1, sizeof(xce_resval_data_t));
break;
default:
rc = -EINVAL;
break;
}
if (rc < 0) {
/* FIXME we need to reply no data */
return rc;
}
if (pktlimit <= 0) {
pktlimit = nrows;
}
do {
int nc = nrows - pos;
if (nc > pktlimit) {
nc = pktlimit;
}
if (checkf != NULL) {
checkstatus = (*checkf)(checkarg);
if (checkstatus < 0) {
nc = 0;
}
}
switch (getdata.table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
getdata.data.jobs->n_total = data->data.jobs->n_total;
getdata.data.jobs->pos = pos;
getdata.data.jobs->count = nc;
if (nc == 0) {
getdata.data.jobs->jobs = NULL;
} else {
getdata.data.jobs->jobs = &(data->data.jobs->jobs[pos]);
}
break;
case XceData_data_schedworkers:
case XceData_data_workers:
getdata.data.workers->n_total = data->data.workers->n_total;
getdata.data.workers->pos = pos;
getdata.data.workers->count = nc;
if (nc == 0) {
getdata.data.workers->workers = NULL;
} else {
getdata.data.workers->workers
= &(data->data.workers->workers[pos]);
}
break;
case XceData_data_resources:
getdata.data.resources->n_total = data->data.resources->n_total;
getdata.data.resources->pos = pos;
getdata.data.workers->count = nc;
if (nc == 0) {
getdata.data.resources->resources = NULL;
} else {
getdata.data.resources->resources
= &(data->data.resources->resources[pos]);
}
break;
case XceData_data_resvals:
getdata.data.resvals->n_total = data->data.resvals->n_total;
getdata.data.resvals->pos = pos;
getdata.data.resvals->count = nc;
if (nc == 0) {
getdata.data.resvals->vals = NULL;
} else {
getdata.data.resvals->vals = &(data->data.resvals->vals[pos]);
}
break;
default:
/* should never goes here */
break;
}
rc = xce_reply(ep, from, &reply1);
if (rc < 0) {
break;
}
pos += nc;
} while ((pos < nrows) && (checkstatus >= 0));
switch (getdata.table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
FREEUP(getdata.data.jobs);
break;
case XceData_data_schedworkers:
case XceData_data_workers:
FREEUP(getdata.data.workers);
break;
case XceData_data_resources:
FREEUP(getdata.data.resources);
break;
case XceData_data_resvals:
FREEUP(getdata.data.resvals);
break;
default:
break;
}
return rc;
}
int extract_data(dataset_t* dataset, xce_data_t* getdata)
{
int ntotal;
int rc = 0;
int pos = 0;
int count = 0;
TRACE("%s ...", __func__);
switch (getdata->table) {
case XceData_data_schedjobs:
case XceData_data_jobs:
if (getdata->data.jobs->count <= 0) {
return 1;
}
ntotal = getdata->data.jobs->n_total;
pos = getdata->data.jobs->pos;
count = getdata->data.jobs->count;
if (dataset->rows == NULL) {
dataset->rows = (xce_job_t**)calloc(ntotal, sizeof(xce_job_t*));
dataset->nrows = 0;
}
memcpy((void*)(((char*)dataset->rows) + sizeof(xce_job_t*) * pos),
getdata->data.jobs->jobs, count * sizeof(xce_job_t*));
memset(getdata->data.jobs->jobs, 0x0, count * sizeof(xce_job_t*));
break;
case XceData_data_schedworkers:
case XceData_data_workers:
if (getdata->data.workers->count <= 0) {
return 1;
}
ntotal = getdata->data.workers->n_total;
if (dataset->rows == NULL) {
dataset->rows
= (xce_worker_t**)calloc(ntotal, sizeof(xce_worker_t*));
dataset->nrows = 0;
}
pos = getdata->data.workers->pos;
count = getdata->data.workers->count;
memcpy((void*)(((char*)dataset->rows) + sizeof(xce_worker_t*) * pos),
getdata->data.workers->workers, count * sizeof(xce_worker_t*));
memset(getdata->data.workers->workers, 0x0,
count * sizeof(xce_worker_t*));
break;
case XceData_data_resources:
if (getdata->data.resources->count <= 0) {
return 1;
}
ntotal = getdata->data.resources->n_total;
if (dataset->rows == NULL) {
dataset->rows
= (xce_resource_t**)calloc(ntotal, sizeof(xce_resource_t*));
dataset->nrows = 0;
}
pos = getdata->data.resources->pos;
count = getdata->data.resources->count;
memcpy((void*)(((char*)dataset->rows) + sizeof(xce_resource_t*) * pos),
getdata->data.resources->resources,
count * sizeof(xce_resource_t*));
memset(getdata->data.resources->resources, 0x0,
count * sizeof(xce_resource_t*));
break;
case XceData_data_resvals:
if (getdata->data.resvals->count <= 0) {
return 1;
}
ntotal = getdata->data.resvals->n_total;
if (dataset->rows == NULL) {
dataset->rows
= (xce_resval_t**)calloc(ntotal, sizeof(xce_resval_t*));
dataset->nrows = 0;
}
pos = getdata->data.resvals->pos;
count = getdata->data.resvals->count;
memcpy((void*)(((char*)dataset->rows) + sizeof(xce_resval_t*) * pos),
getdata->data.resvals->vals, count * sizeof(xce_resval_t*));
memset(getdata->data.resvals->vals, 0x0, count * sizeof(xce_resval_t*));
break;
case XceData_data_jobArray:
/* job-array-single-wal-record task 5.3b: single, unchunked JobSubmit
* descriptor (base jobid, array_size, one shared spec) -- unlike the
* LIST-shaped cases above, no pagination/accumulation loop is needed:
* xcesched_push_jobarray() (sched.c) and apiserver_push_jobarray()
* (apiserver.c) always send exactly one complete descriptor per
* commit. Transfer ownership of the decoded pointer straight into
* dataset->rows (mirrors the LIST cases' ownership-transfer intent,
* without an array-of-pointers layer since there is only one row)
* and signal reassembly complete immediately -- returning here skips
* the shared ntotal/pos-based tail below, which does not apply to a
* single-shot payload. */
if (getdata->data.job_array == NULL) {
return -EINVAL;
}
dataset->rows = (void*)getdata->data.job_array;
dataset->nrows = 1;
getdata->data.job_array = NULL;
return 1;
default:
rc = -EINVAL;
break;
}
if (rc < 0) {
return rc;
}
dataset->nrows += count;
if (dataset->nrows < ntotal) {
return 0;
}
return 1;
}
int xce_jobdispatch_req(endpoint_t* ep, xce_job_dispatch_t* info)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_jobdispatch,
.body_which = Request_body_dispatchmsg,
.body = {
.dispatchmsg = info
}
};
/* clang-format on */
/* clang-format off */
if ((ep == NULL) ||
(info == NULL)) {
return -EINVAL;
}
/* clang-format on */
return xce_request(ep, &request);
}
int xce_notify_req(endpoint_t* ep, xce_notify_data_t* data)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_notify,
.body_which = Request_body_notifymsg,
.body = {
.notifymsg = data
}
};
/* clang-format on */
if ((ep == NULL) || (data == NULL)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_jobsubmit_req(endpoint_t* ep, xce_job_submit_t* submit)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_jobsub,
.body_which = Request_body_jobsubmsg,
.body = {
.jobsubmsg = submit
}
};
/*clang-format on*/
/* clang-format off */
if ((ep == NULL) ||
(submit == NULL)) {
return -EINVAL;
}
/* clang-format on */
return xce_request(ep, &request);
}
int xce_startjob_req(endpoint_t* ep, xce_job_exec_t* execinfo)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_startjob,
.body_which = Request_body_jobstartmsg,
.body = {
.jobstartmsg = execinfo
}
};
/* clang-format on */
return xce_request(ep, &request);
}
int xce_clusterconf_req(endpoint_t* ep)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_clusterconf,
.body_which = Request_body_nomsg
};
/* clang-format on */
return xce_request(ep, &request);
}
int xce_ctrlstatus_req(endpoint_t* ep)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_ctrlstatus,
.body_which = Request_body_nomsg
};
/* clang-format on */
return xce_request(ep, &request);
}
int xce_update_req(endpoint_t* ep, xce_update_data_t* data)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_update,
.body_which = Request_body_updatemsg,
.body = {
.updatemsg = data
}
};
/* clang-format on */
if ((ep == NULL) || (data == NULL)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_jobctrl_req(endpoint_t* ep, xce_jobctrl_t* ctrinfo)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_jobcontrol,
.body_which = Request_body_jobctrlmsg,
.body = {
.jobctrlmsg = ctrinfo
}
};
/* clang-format on */
if ((ep == NULL) || (ctrinfo == NULL)) {
return -EINVAL;
}
if (ctrinfo->jobid == 0) {
return -EINVAL;
}
switch (ctrinfo->action) {
case JobControl_u_siginfo:
break;
case JobControl_u_ckptoption:
if (ctrinfo->u.ckptoption->method == NULL) {
ctrinfo->u.ckptoption->method = "default";
}
break;
default:
return -EINVAL;
break;
}
return xce_request(ep, &request);
}
int xce_queuectrl_req(endpoint_t* ep, xce_queuectrl_t* ctrlinfo)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_queuecontrol,
.body_which = Request_body_queuectrlmsg,
.body = {
.queuectrlmsg = ctrlinfo
}
};
/* clang-format on */
if ((ep == NULL) || (ctrlinfo == NULL)) {
return -EINVAL;
}
if ((ctrlinfo->queue == NULL) || (strlen(ctrlinfo->queue) == 0)) {
return -EINVAL;
}
if (ctrlinfo->action == QUEUE_DO_NONE) {
return 0;
}
if ((ctrlinfo->action < 0) || (ctrlinfo->action > QUEUE_ACTIVE)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_hostctrl_req(endpoint_t* ep, xce_hostctrl_t* ctrlinfo)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_hostcontrol,
.body_which = Request_body_hostctrlmsg,
.body = {
.hostctrlmsg = ctrlinfo
}
};
/* clang-format on */
if ((ep == NULL) || (ctrlinfo == NULL)) {
return -EINVAL;
}
if ((ctrlinfo->hostname == NULL) || (strlen(ctrlinfo->hostname) == 0)) {
return -EINVAL;
}
if (ctrlinfo->action == HOST_DO_NONE) {
return 0;
}
if (ctrlinfo->action != HOST_OPEN && ctrlinfo->action != HOST_CLOSE) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_runtask_req(endpoint_t* ep, xce_taskrun_t* runinfo)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_runtasks,
.body_which = Request_body_runtaskmsg,
.body = {
.runtaskmsg = runinfo
}
};
/* clang-format on */
if ((ep == NULL) || (runinfo == NULL)) {
return -EINVAL;
}
if (runinfo->nhosts <= 0) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_set_pend_reason_req(endpoint_t* ep, xce_preason_set_t* reasons)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_setpreason,
.body_which = Request_body_preasonmsg,
.body = {
.preasonmsg = reasons
}
};
/* clang-format on */
if ((ep == NULL) || (reasons == NULL)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_clear_reason_req(endpoint_t* ep, xce_clear_reason_t* reason)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_clrpreason,
.body_which = Request_body_clrpreasonmsg,
.body = {
.clrpreasonmsg = reason
}
};
/* clang-format on */
if ((ep == NULL) || (reason == NULL)) {
return -EINVAL;
}
return xce_request(ep, &request);
}
int xce_fetchfile_req(endpoint_t* ep, xce_fetchfile_t* msg)
{
/* clang-format off */
xce_request_t request = {
.opcode = OPCode_fetchfile,
.body_which = Request_body_fetchfilemsg,
.body = {
.fetchfilemsg = msg
}
};
/* clang-format on */
if ((ep == NULL) || (msg == NULL)) {
return -EINVAL;
}
if (msg->filepath == NULL || msg->filepath[0] == '\0') {
return -EINVAL;
}
return xce_request(ep, &request);
}
/* ── Client-Side Transparent Retry tunables (Req 6.1, 6.2) ─────────────────
*
* XCE_RETRY_DEFAULTS provides safe production defaults for the failover retry
* loop that lives in the libxce client layer (client.c / client_failover.c).
* The orphaned no-op xce_request_with_retry() primitive and its
* xce_retry_send_hook test seam were removed; they are superseded by the
* xcecli_* failover loop.
*/
const xce_retry_config_t XCE_RETRY_DEFAULTS = {
.max_retries = 20,
.initial_retry_ms = 50,
.max_retry_ms = 5000,
.backoff_multiplier = 2.0,
};