1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00

TempSeat: fix output interleaving.

Working on the previous commit, I suddenly realised I'd made a mistake
in the design of TempSeat: you can't buffer standard output and
standard error separately and then replay them one after another,
because the interleaving of the two kinds of output might also be
significant. (Especially if the consuming Seat doesn't separate them.)

Now TempSeat has a single bufchain for all the data, paralleled by a
linked list describing each contiguous chunk of it consisting of a
single output type. So we can replay the data with both the correct
separation _and_ the correct order.
This commit is contained in:
Simon Tatham 2021-09-16 15:03:42 +01:00
parent ac47e550c6
commit 71cb9ca487

View File

@ -14,10 +14,28 @@
#include "putty.h"
struct output_chunk {
struct output_chunk *next;
SeatOutputType type;
size_t size;
};
typedef struct TempSeat TempSeat;
struct TempSeat {
Seat *realseat;
bufchain outputs[3]; /* stdout, stderr, auth banner (just in case) */
/*
* Single bufchain to hold all the buffered output, regardless of
* its type.
*/
bufchain output;
/*
* List of pieces of that bufchain that are intended for one or
* another output destination
*/
struct output_chunk *outchunk_head, *outchunk_tail;
bool seen_session_started;
bool seen_remote_exit;
bool seen_remote_disconnect;
@ -38,14 +56,24 @@ static size_t tempseat_output(Seat *seat, SeatOutputType type,
{
TempSeat *ts = container_of(seat, TempSeat, seat);
size_t index = (size_t)type;
assert(index < lenof(ts->outputs));
bufchain_add(&ts->outputs[index], data, len);
bufchain_add(&ts->output, data, len);
size_t total_size = 0;
for (size_t i = 0; i < lenof(ts->outputs); i++)
total_size += bufchain_size(&ts->outputs[i]);
return total_size;
if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) {
struct output_chunk *new_chunk = snew(struct output_chunk);
new_chunk->type = type;
new_chunk->size = 0;
new_chunk->next = NULL;
if (ts->outchunk_tail)
ts->outchunk_tail->next = new_chunk;
else
ts->outchunk_head = new_chunk;
ts->outchunk_tail = new_chunk;
}
ts->outchunk_tail->type += len;
return bufchain_size(&ts->output);
}
static void tempseat_notify_session_started(Seat *seat)
@ -302,8 +330,8 @@ Seat *tempseat_new(Seat *realseat)
ts->seat.vt = &tempseat_vt;
ts->realseat = realseat;
for (size_t i = 0; i < lenof(ts->outputs); i++)
bufchain_init(&ts->outputs[i]);
bufchain_init(&ts->output);
ts->outchunk_head = ts->outchunk_tail = NULL;
return &ts->seat;
}
@ -324,8 +352,12 @@ void tempseat_free(Seat *seat)
{
assert(seat->vt == &tempseat_vt);
TempSeat *ts = container_of(seat, TempSeat, seat);
for (unsigned i = 0; i < 2; i++)
bufchain_clear(&ts->outputs[i]);
bufchain_clear(&ts->output);
while (ts->outchunk_head) {
struct output_chunk *chunk = ts->outchunk_head;
ts->outchunk_head = chunk->next;
sfree(chunk);
}
sfree(ts);
}
@ -334,15 +366,29 @@ void tempseat_flush(Seat *seat)
assert(seat->vt == &tempseat_vt);
TempSeat *ts = container_of(seat, TempSeat, seat);
/* Empty the output bufchains into the real seat */
for (size_t i = 0; i < lenof(ts->outputs); i++) {
while (bufchain_size(&ts->outputs[i])) {
ptrlen pl = bufchain_prefix(&ts->outputs[i]);
seat_output(ts->realseat, i, pl.ptr, pl.len);
bufchain_consume(&ts->outputs[i], pl.len);
/* Empty the output bufchains into the real seat, taking care to
* preserve both separation and interleaving */
while (bufchain_size(&ts->output)) {
ptrlen pl = bufchain_prefix(&ts->output);
assert(ts->outchunk_head);
struct output_chunk *chunk = ts->outchunk_head;
if (pl.len > chunk->size)
pl.len = chunk->size;
seat_output(ts->realseat, chunk->type, pl.ptr, pl.len);
bufchain_consume(&ts->output, pl.len);
chunk->size -= pl.len;
if (chunk->size == 0) {
ts->outchunk_head = chunk->next;
sfree(chunk);
}
}
/* That should have exactly emptied the output chunk list too */
assert(!ts->outchunk_head);
/* Pass on any other kinds of event we've buffered */
if (ts->seen_session_started)
seat_notify_session_started(ts->realseat);