commit - 6700e6d8bc89354c9145ee84ad4d8a3a666cc8c2
commit + c07b6527d8b68986949d7889496b04f6756cbba1
blob - fe102d4dafbcf9bd82f474cd2053e352e8bcc847
blob + a8022ac95b25226875c4ad607b6180a345ec96b0
--- regress/README
+++ regress/README
steady pledge before untrusted
input; forbidden syscalls trip
PLDG violations)
-I10 mutual distrust across roles imsgev (partially consumed payloads
- and checked-send failures fail closed);
+I10 mutual distrust across roles imsgev (partially consumed payloads,
+ checked sends, and bounded chunk
+ failures fail closed);
generation (canonical event ordering,
bounds, and exactly one terminal);
ui_config (typed ordering, bounded staging,
integration); turn (cache/SIGINT path)
src/fugu-tty/ui_config_rx.c ui_config; turn (full-palette PTY path)
src/common/imsgev.c imsgev (whole-payload views, checked
- normal/teardown sends, event failures);
+ normal/teardown sends, bounded chunk
+ emission, event failures);
sandbox (event runtime)
src/common/worker.c sandbox (worker runtime)
src/fugu-api (request/stream) turn (correlated concurrent lead/agent
blob - 6c3766888c8fd793a430fc901c944614bfa62815
blob + 76378954702734712cbd8768607882c56c6553d8
--- regress/imsgev/imsgev_test.c
+++ regress/imsgev/imsgev_test.c
#include <imsg.h>
#include <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
return (imsgbuf_get(receiver, imsg));
}
+static void
+check_chunks(size_t len)
+{
+ struct imsgev_payload payload;
+ struct imsgbuf receiver;
+ struct imsgev iev;
+ struct imsg imsg;
+ u_char *data;
+ size_t frames, i, n, off;
+ int fds[2], space = 128 * 1024;
+
+ data = malloc(len);
+ if (data == NULL)
+ err(1, "malloc chunk test");
+ for (i = 0; i < len; i++)
+ data[i] = (u_char)((i * 37 + 11) & 0xff);
+ frames = 1 + (len - 1) / IMSGEV_CHUNK_MAX;
+ channel_init(&iev, &receiver, fds);
+ if (setsockopt(fds[0], SOL_SOCKET, SO_SNDBUF, &space,
+ sizeof(space)) == -1 ||
+ setsockopt(fds[1], SOL_SOCKET, SO_RCVBUF, &space,
+ sizeof(space)) == -1)
+ err(1, "setsockopt chunk test");
+ imsgev_regress_reset();
+ imsgev_send_chunks(&iev, TEST_DATA, 0x12345678, data, len);
+ CHECK(imsgbuf_queuelen(&iev.ibuf) == frames);
+ CHECK(imsgev_regress_calls(IMSGEV_REGRESS_COMPOSE) == frames);
+ CHECK(imsgev_regress_calls(IMSGEV_REGRESS_EVENT_DEL) == frames);
+ CHECK(imsgev_regress_calls(IMSGEV_REGRESS_EVENT_ADD) == frames);
+ if (imsgbuf_flush(&iev.ibuf) == -1)
+ err(1, "imsgbuf_flush chunks");
+ for (i = off = 0; i < frames; i++, off += n) {
+ while (imsgbuf_get(&receiver, &imsg) == 0)
+ if (imsgbuf_read(&receiver) != 1)
+ errx(1, "imsgbuf_read chunks");
+ n = len - off;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
+ CHECK(imsg.hdr.type == TEST_DATA &&
+ imsg.hdr.peerid == 0x12345678);
+ CHECK(imsgev_get_payload(&imsg, &payload) == 0);
+ CHECK(payload.len == n && payload.data != NULL &&
+ memcmp(payload.data, data + off, n) == 0);
+ imsg_free(&imsg);
+ }
+ CHECK(off == len);
+ CHECK(imsgbuf_get(&receiver, &imsg) == 0);
+ channel_clear(&iev, &receiver, fds);
+ free(data);
+}
+
static int
dies(void (*fn)(void))
{
}
static int
-failed_sequence(unsigned int fail_at, uint32_t *trace, size_t *trace_len)
+failed_exchange(enum imsgev_regress_op op, unsigned int fail_at,
+ int null_payload, uint32_t *trace, size_t *trace_len)
{
+ u_char data[2 * IMSGEV_CHUNK_MAX + 1];
struct imsgbuf receiver;
struct imsgev iev;
ssize_t n;
channel_init(&iev, &receiver, fds);
imsgev_regress_reset();
imsgev_regress_trace(pfd[1]);
- imsgev_regress_fail(IMSGEV_REGRESS_COMPOSE, fail_at);
- imsgev_send(&iev, TEST_DATA, 0, "a", 1);
- imsgev_send(&iev, TEST_DATA, 0, "b", 1);
+ if (null_payload)
+ imsgev_send_chunks(&iev, TEST_DATA, 0, NULL, 1);
+ memset(data, 0xa5, sizeof(data));
+ imsgev_regress_fail(op, fail_at);
+ imsgev_send_chunks(&iev, TEST_DATA, 0, data, sizeof(data));
imsgev_send(&iev, TEST_END, 0, NULL, 0);
_exit(0);
default:
if (event_init() == NULL)
errx(1, "event_init");
+ /* Empty payloads emit no data frames; the exchange owns its terminal. */
+ channel_init(&iev, &receiver, fds);
+ imsgev_regress_reset();
+ imsgev_send_chunks(&iev, TEST_DATA, 7, NULL, 0);
+ imsgev_send_chunks(&iev, TEST_DATA, 7, binary, 0);
+ imsgev_send(&iev, TEST_END, 7, NULL, 0);
+ CHECK(imsgbuf_queuelen(&iev.ibuf) == 1);
+ CHECK(imsgev_regress_calls(IMSGEV_REGRESS_COMPOSE) == 1);
+ CHECK(receive_one(&iev, &receiver, &imsg) == 1);
+ CHECK(imsg.hdr.type == TEST_END && imsg.hdr.peerid == 7 &&
+ imsg_get_len(&imsg) == 0);
+ imsg_free(&imsg);
+ channel_clear(&iev, &receiver, fds);
+
+ /* Exact boundaries preserve bytes, frame size, type, and correlation. */
+ check_chunks(1);
+ check_chunks(IMSGEV_CHUNK_MAX - 1);
+ check_chunks(IMSGEV_CHUNK_MAX);
+ check_chunks(IMSGEV_CHUNK_MAX + 1);
+ check_chunks(2 * IMSGEV_CHUNK_MAX);
+ check_chunks(2 * IMSGEV_CHUNK_MAX + 1);
+
make_imsg(NULL, 0, &receiver, &imsg);
payload.data = (const u_char *)1;
payload.len = 1;
CHECK(dies(die_rearm_add));
CHECK(dies(die_clear_del));
- /* A failed chunk stops the exchange before any terminal send. */
- CHECK(failed_sequence(1, trace, &trace_len));
+ /* NULL/nonzero is rejected before even the first composition. */
+ CHECK(failed_exchange(IMSGEV_REGRESS_OPS, 0, 1, trace, &trace_len));
+ CHECK(trace_len == 0);
+
+ /* A failed data chunk stops the exchange before any later terminal. */
+ CHECK(failed_exchange(IMSGEV_REGRESS_COMPOSE, 1, 0,
+ trace, &trace_len));
CHECK(trace_len == 1 && trace[0] == TEST_DATA);
- CHECK(failed_sequence(2, trace, &trace_len));
+ CHECK(failed_exchange(IMSGEV_REGRESS_COMPOSE, 2, 0,
+ trace, &trace_len));
CHECK(trace_len == 2 && trace[0] == TEST_DATA &&
trace[1] == TEST_DATA);
- CHECK(failed_sequence(3, trace, &trace_len));
+ CHECK(failed_exchange(IMSGEV_REGRESS_COMPOSE, 3, 0,
+ trace, &trace_len));
CHECK(trace_len == 3 && trace[0] == TEST_DATA &&
- trace[1] == TEST_DATA && trace[2] == TEST_END);
+ trace[1] == TEST_DATA && trace[2] == TEST_DATA);
+ CHECK(failed_exchange(IMSGEV_REGRESS_COMPOSE, 4, 0,
+ trace, &trace_len));
+ CHECK(trace_len == 4 && trace[0] == TEST_DATA &&
+ trace[1] == TEST_DATA && trace[2] == TEST_DATA &&
+ trace[3] == TEST_END);
+ /* Every chunk inherits the checked event re-arm policy. */
+ CHECK(failed_exchange(IMSGEV_REGRESS_EVENT_DEL, 2, 0,
+ trace, &trace_len));
+ CHECK(trace_len == 2 && trace[0] == TEST_DATA &&
+ trace[1] == TEST_DATA);
+ CHECK(failed_exchange(IMSGEV_REGRESS_EVENT_ADD, 2, 0,
+ trace, &trace_len));
+ CHECK(trace_len == 2 && trace[0] == TEST_DATA &&
+ trace[1] == TEST_DATA);
+
/* Teardown is synchronous, best effort, and never re-arms events. */
channel_init(&iev, &receiver, fds);
imsgev_regress_reset();
blob - a871534c0365bae20aa5ea5ee4e649d5bc73f8dd
blob + f08725e30d20ea6b7831cbda22421c098dea831d
--- src/common/imsgev.c
+++ src/common/imsgev.c
imsgev_add(iev);
}
+void
+imsgev_send_chunks(struct imsgev *iev, uint32_t type, uint32_t peerid,
+ const void *data, size_t datalen)
+{
+ const u_char *p = data;
+ size_t off, n;
+
+ if (data == NULL && datalen != 0)
+ fatalx("NULL chunk payload");
+ for (off = 0; off < datalen; off += n) {
+ n = datalen - off;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
+ imsgev_send(iev, type, peerid, p + off, n);
+ }
+}
+
/*
* Teardown has no event-loop future in which queued output can drain.
* Attempt one synchronous empty frame and flush, but never turn cleanup
blob - 17920eec05b8c5a24a21b4d3d47c72941dd0298f
blob + 1ceb3f1eda3dd126f60dcacb3cb3e8137bf036e9
--- src/common/imsgev.h
+++ src/common/imsgev.h
#include <event.h>
#include <imsg.h>
+/* A conservative payload slice below imsg's 16 KiB frame ceiling. */
+#define IMSGEV_CHUNK_MAX (8 * 1024)
+
/*
* The imsg-over-libevent wrapper, in the vmd proc.c lineage: one
* struct per channel, write interest re-armed only when output is
/* Normal sends are checked and own the no-fd/no-pid transport policy. */
void imsgev_send(struct imsgev *, uint32_t, uint32_t, const void *,
size_t);
+/*
+ * Emit a contiguous payload as checked bounded frames. A zero length emits
+ * none; a nonzero length requires data. Typed callers own terminal frames.
+ */
+void imsgev_send_chunks(struct imsgev *, uint32_t, uint32_t, const void *,
+ size_t);
/* Teardown attempts one synchronous empty frame without re-arming events. */
void imsgev_send_teardown(struct imsgev *, uint32_t);
int imsgev_get_payload(struct imsg *, struct imsgev_payload *);
blob - b995cdd31bd24787f89b407a6beeb22d9b533c9f
blob + 942a23cf5d400d1e79a25735bb7088635fd8235f
--- src/fugu/coord.c
+++ src/fugu/coord.c
#define MAX_TOOL_CALLS 64 /* tool_use blocks per assistant msg */
#define MODELS_RELAY_MAX 4096 /* picker entries per listing (I10) */
#define AGENT_WEB_MEMORY_MAX (64 * 1024 * 1024)
+#define CAPTURE_TEXT_MAX (8 * 1024) /* one bounded CTX_APPEND frame */
/* The /compact summarization instruction (behavior.md section 3). */
#define COMPACT_INSTR \
{
struct subagent *a;
struct tool_req req;
- size_t off;
int i;
if (c->agent_web_active != NULL || c->web_busy_id != 0)
c->web_busy_id = a->web_id;
imsgev_send(&c->iev[ROLE_WEB], FUGU_IMSG_WEB_REQ,
a->web_id, &req, sizeof(req));
- for (off = 0; off < a->web_arg.len; ) {
- size_t k = a->web_arg.len - off;
-
- if (k > FUGU_CHUNK)
- k = FUGU_CHUNK;
- imsgev_send(&c->iev[ROLE_WEB], FUGU_IMSG_WEB_ARG,
- a->web_id, a->web_arg.data + off, k);
- off += k;
- }
+ imsgev_send_chunks(&c->iev[ROLE_WEB], FUGU_IMSG_WEB_ARG,
+ a->web_id, a->web_arg.data, a->web_arg.len);
imsgev_send(&c->iev[ROLE_WEB], FUGU_IMSG_WEB_RUN,
a->web_id, NULL, 0);
agent_web_arg_free(c, a);
{
struct imsgev *iev = &c->iev[ROLE_API];
struct api_request_end end;
- size_t off;
- for (off = 0; off < body->len; ) {
- size_t n = body->len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(iev, FUGU_IMSG_REQUEST, id,
- body->data + off, n);
- off += n;
- }
+ imsgev_send_chunks(iev, FUGU_IMSG_REQUEST, id,
+ body->data, body->len);
memset(&end, 0, sizeof(end));
end.provider_slot = provider_slot;
imsgev_send(iev, FUGU_IMSG_REQUEST_END, id, &end,
{
struct imsgev *iev = &c->iev[ROLE_TOOL];
struct tool_req req;
- size_t off;
memset(&req, 0, sizeof(req));
strlcpy(req.name, tc->name, sizeof(req.name));
imsgev_send(iev, FUGU_IMSG_TOOL_REQ, 0, &req, sizeof(req));
- for (off = 0; off < tc->input.len; ) {
- size_t n = tc->input.len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(iev, FUGU_IMSG_TOOL_ARG, 0,
- tc->input.data + off, n);
- off += n;
- }
+ imsgev_send_chunks(iev, FUGU_IMSG_TOOL_ARG, 0,
+ tc->input.data, tc->input.len);
imsgev_send(iev, FUGU_IMSG_TOOL_RUN, 0, NULL, 0);
buf_reset(&c->tool_out);
{
struct imsgev *iev = &c->iev[ROLE_WEB];
struct tool_req req;
- size_t off;
int rc;
buf_reset(&c->tool_out);
c->web_id = agent_unique_id(c);
c->web_busy_id = c->web_id;
imsgev_send(iev, FUGU_IMSG_WEB_REQ, c->web_id, &req, sizeof(req));
- for (off = 0; off < tc->input.len; ) {
- size_t n = tc->input.len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(iev, FUGU_IMSG_WEB_ARG, c->web_id,
- tc->input.data + off, n);
- off += n;
- }
+ imsgev_send_chunks(iev, FUGU_IMSG_WEB_ARG, c->web_id,
+ tc->input.data, tc->input.len);
imsgev_send(iev, FUGU_IMSG_WEB_RUN, c->web_id, NULL, 0);
c->wait = WAIT_WEB;
}
static void
-agent_compose_bytes(struct imsgev *iev, uint32_t type, const void *data,
- size_t len)
-{
- const u_char *p = data;
- size_t off, n;
-
- for (off = 0; off < len; off += n) {
- n = len - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(iev, type, 0, p + off, n);
- }
-}
-
-static void
agent_start(struct coord *c, struct subagent *a, const struct buf *system)
{
struct agent_start start;
buf_addc(&b, '\0');
imsgev_send(&a->iev, FUGU_IMSG_AGENT_START, 0, b.data, b.len);
buf_free(&b);
- agent_compose_bytes(&a->iev, FUGU_IMSG_AGENT_SYSTEM, system->data,
+ imsgev_send_chunks(&a->iev, FUGU_IMSG_AGENT_SYSTEM, 0, system->data,
system->len);
- agent_compose_bytes(&a->iev, FUGU_IMSG_AGENT_PROMPT, a->prompt.data,
+ imsgev_send_chunks(&a->iev, FUGU_IMSG_AGENT_PROMPT, 0, a->prompt.data,
a->prompt.len);
imsgev_send(&a->iev, FUGU_IMSG_AGENT_RUN, 0, NULL, 0);
}
return (1);
}
/* the whole append must fit one imsg frame; keep well under it */
- if (strlen(text) > FUGU_CHUNK) {
+ if (strlen(text) > CAPTURE_TEXT_MAX) {
emit_error(c, "fugu: capture line too long");
return (1);
}
blob - 34516048ceae9b0faa99bce0196bb2d6681151d6
blob + 37523910880611bd05f9b3059637d3487170f0a5
--- src/fugu/proto.h
+++ src/fugu/proto.h
#define PROVIDER_ANTHROPIC 0
#define PROVIDER_OPENAI 1
-/* Body chunk size for the REQUEST stream (below the 16 KiB imsg cap). */
-#define FUGU_CHUNK (8 * 1024)
-
/* Credentials cross one HELLO frame and later become HTTP headers. */
#define FUGU_SECRET_MAX (4 * 1024)
#define FUGU_TOOL_NAME_MAX 32
/* The network worker uses its own authority-separated exchange. */
-#define FUGU_WEB_HELLO_MAX FUGU_CHUNK
+#define FUGU_WEB_HELLO_MAX (8 * 1024)
#define FUGU_WEB_ARG_MAX FUGU_TOOL_ARG_MAX
#define FUGU_WEB_OUT_MAX (256 * 1024)
blob - c6f79ced52336f1652d68757b1b3409f1ade861f
blob + 3b8b8723d9c7524969a9ff6212020e3950c0a91f
--- src/fugu-api/main.c
+++ src/fugu-api/main.c
static void
relay_bytes(struct api_request *r, uint32_t type, const void *data, size_t len)
{
- const u_char *p = data;
- size_t off, n;
-
- for (off = 0; off < len; off += n) {
- n = len - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- relay(r, type, p + off, n);
- }
+ imsgev_send_chunks(&r->worker->iev, type, r->id, data, len);
}
static void
struct buf b;
n = len - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
buf_init(&b);
buf_add(&b, &index, sizeof(index));
buf_add(&b, p + off, n);
for (off = 0; off < ev->textlen; ) {
size_t n = ev->textlen - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
if (filter_text(r, ev->text + off, n, 0) == -1) {
relay_error(r,
"provider response contained an API credential");
for (off = 0; off < ev->partiallen; ) {
size_t n = ev->partiallen - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
if (filter_tool(r, ts, ev->partial + off, n, 0) == -1) {
relay_error(r,
"provider response contained an API credential");
blob - a8a09dd372615a9daec10e4c7d8ee5ac4e2ecfdf
blob + 069e63f2ee77a067e43d2b3a5a003715b89581dd
--- src/fugu-editor/main.c
+++ src/fugu-editor/main.c
{
struct stat st;
char path[] = "/tmp/fugu-edit.XXXXXXXXXX";
- char tmp[FUGU_CHUNK];
+ char tmp[IMSGEV_CHUNK_MAX];
ssize_t n;
size_t off, sent;
sigset_t oldmask;
blob - 83c5b7c960279f612572e5e849cb4f2e3152ad0b
blob + 1a4fe85c811b6807de7951646229f2ad3e684147
--- src/fugu-tool/agent.c
+++ src/fugu-tool/agent.c
for (off = 0; off < len; off += n) {
n = len - off;
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
+ if (n > IMSGEV_CHUNK_MAX)
+ n = IMSGEV_CHUNK_MAX;
agent_send(a, type, p + off, n);
}
}
blob - 07b7f6d8e57a4e1802416d7fe25b7a3b420d0546
blob + 7302d1885910203803ab6bdd772ce678a258f08b
--- src/fugu-tool/main.c
+++ src/fugu-tool/main.c
int is_error)
{
struct tool_result r;
- size_t off, dlen;
+ size_t dlen;
- for (off = 0; off < out->len; ) {
- size_t n = out->len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(&w->iev, FUGU_IMSG_TOOL_OUT, 0,
- out->data + off, n);
- off += n;
- }
+ imsgev_send_chunks(&w->iev, FUGU_IMSG_TOOL_OUT, 0,
+ out->data, out->len);
dlen = diff->len > FUGU_TOOL_DIFF_MAX ? FUGU_TOOL_DIFF_MAX : diff->len;
- for (off = 0; off < dlen; ) {
- size_t n = dlen - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(&w->iev, FUGU_IMSG_TOOL_DIFF, 0,
- diff->data + off, n);
- off += n;
- }
+ imsgev_send_chunks(&w->iev, FUGU_IMSG_TOOL_DIFF, 0,
+ diff->data, dlen);
memset(&r, 0, sizeof(r));
r.is_error = is_error;
imsgev_send(&w->iev, FUGU_IMSG_TOOL_RESULT, 0, &r,
case FUGU_IMSG_CTX_READ: {
struct buf ctx;
char path[1024];
- size_t off;
/* the payload is a NUL-terminated path (coordinator-built) */
if (imsgev_get_payload(imsg, &payload) == -1)
path[len] = '\0';
buf_init(&ctx);
ctx_file_read(path, &ctx);
- for (off = 0; off < ctx.len; ) { /* chunk under the cap */
- size_t k = ctx.len - off;
-
- if (k > FUGU_CHUNK)
- k = FUGU_CHUNK;
- imsgev_send(&w->iev, FUGU_IMSG_CTX_DATA, 0,
- ctx.data + off, k);
- off += k;
- }
+ imsgev_send_chunks(&w->iev, FUGU_IMSG_CTX_DATA, 0,
+ ctx.data, ctx.len);
imsgev_send(&w->iev, FUGU_IMSG_CTX_END, 0, NULL, 0);
buf_free(&ctx);
return (0);
blob - eece9db7071abfafc31067ad2192b24c8a84511a
blob + 05f771be2db08fe8748ce411937e60884c6fc586
--- src/fugu-tty/ui.c
+++ src/fugu-tty/ui.c
static void arm_esc(struct ui *);
/*
- * Send a message to the coordinator in FUGU_CHUNK slices terminated by
+ * Send a message to the coordinator in bounded slices terminated by
* an end marker (behavior.md 2.1): a submitted line or a steered entry
* can be a whole paste (up to 64 KiB), which does not fit one imsg
* frame (16 KiB). The editor caps a message at ED_PASTE_MAX, so the
send_chunked(struct ui *u, int chunk_type, int end_type, const char *p,
size_t len)
{
- size_t off;
-
- for (off = 0; off < len; ) {
- size_t n = len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(&u->w->iev, chunk_type, 0, p + off, n);
- off += n;
- }
+ imsgev_send_chunks(&u->w->iev, chunk_type, 0, p, len);
imsgev_send(&u->w->iev, end_type, 0, NULL, 0);
}
static void
compose_begin(struct ui *u)
{
- size_t len, off;
+ size_t len;
const char *text = ed_text(&u->ed, &len);
if (u->composing)
event_del(&u->ev_in);
(void)write(STDOUT_FILENO, "\033[?2004l", 8); /* paste off */
endwin();
- for (off = 0; off < len; ) {
- size_t n = len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(&u->w->iev, FUGU_IMSG_UI_COMPOSE, 0,
- text + off, n);
- off += n;
- }
+ imsgev_send_chunks(&u->w->iev, FUGU_IMSG_UI_COMPOSE, 0, text, len);
imsgev_send(&u->w->iev, FUGU_IMSG_UI_COMPOSE_GO, 0,
NULL, 0);
u->composing = 1;
blob - c2665884939d8193e3321c1b2f77f94048dae9bd
blob + 5de80b1424edcb87c8f49ed39b04f63ac360650d
--- src/fugu-web/main.c
+++ src/fugu-web/main.c
int is_error)
{
struct tool_result r;
- size_t off;
- for (off = 0; off < out->len; ) {
- size_t n = out->len - off;
-
- if (n > FUGU_CHUNK)
- n = FUGU_CHUNK;
- imsgev_send(&w->iev, FUGU_IMSG_WEB_OUT, reqid,
- out->data + off, n);
- off += n;
- }
+ imsgev_send_chunks(&w->iev, FUGU_IMSG_WEB_OUT, reqid,
+ out->data, out->len);
memset(&r, 0, sizeof(r));
r.is_error = is_error;
imsgev_send(&w->iev, FUGU_IMSG_WEB_RESULT, reqid, &r, sizeof(r));