+# returns 1 if done, 0 if incomplete
+sub flush_write ($) {
+ my ($self) = @_;
+ my $sock = $self->{sock} or return;
+ my $wbuf = $self->{wbuf} or return 1;
+
+next_buf:
+ while (my $bref = $wbuf->[0]) {
+ if (ref($bref) ne 'CODE') {
+ while ($sock) {
+ my $w = send_tmpio($sock, $bref); # bref is tmpio
+ if (defined $w) {
+ if ($w == 0) {
+ shift @$wbuf;
+ goto next_buf;
+ }
+ } elsif ($! == EAGAIN) {
+ epwait($sock, epbit($sock, EPOLLOUT) | EPOLLONESHOT);
+ return 0;
+ } else {
+ return $self->close;
+ }
+ }
+ } else { #(ref($bref) eq 'CODE') {
+ shift @$wbuf;
+ my $before = scalar(@$wbuf);
+ $bref->($self);
+
+ # bref may be enqueueing more CODE to call (see accept_tls_step)
+ return 0 if (scalar(@$wbuf) > $before);
+ }
+ } # while @$wbuf
+
+ delete $self->{wbuf};
+ 1; # all done
+}
+
+sub rbuf_idle ($$) {
+ my ($self, $rbuf) = @_;
+ if ($$rbuf eq '') { # who knows how long till we can read again
+ delete $self->{rbuf};
+ } else {
+ $self->{rbuf} = $rbuf;
+ }
+}
+
+sub do_read ($$$;$) {
+ my ($self, $rbuf, $len, $off) = @_;
+ my $r = sysread(my $sock = $self->{sock}, $$rbuf, $len, $off // 0);
+ return ($r == 0 ? $self->close : $r) if defined $r;
+ # common for clients to break connections without warning,
+ # would be too noisy to log here:
+ if ($! == EAGAIN) {
+ epwait($sock, epbit($sock, EPOLLIN) | EPOLLONESHOT);
+ rbuf_idle($self, $rbuf);
+ 0;
+ } else {
+ $self->close;
+ }
+}
+
+# drop the socket if we hit unrecoverable errors on our system which
+# require BOFH attention: ENOSPC, EFBIG, EIO, EMFILE, ENFILE...
+sub drop {
+ my $self = shift;
+ carp(@_);
+ $self->close;
+}
+
+# n.b.: use ->write/->read for this buffer to allow compatibility with
+# PerlIO::mmap or PerlIO::scalar if needed
+sub tmpio ($$$) {
+ my ($self, $bref, $off) = @_;
+ my $fh = tmpfile('wbuf', $self->{sock}, O_APPEND) or
+ return drop($self, "tmpfile $!");
+ $fh->autoflush(1);
+ my $len = bytes::length($$bref) - $off;
+ $fh->write($$bref, $len, $off) or return drop($self, "write ($len): $!");
+ [ $fh, 0 ] # [1] = offset, [2] = length, not set by us