std1.0.0-beta-7 SafeC docs

Source io_traits.h

View in the repository

#pragma once
// SafeC Standard Library — byte-stream traits.
//
// std's blocking byte streams share one read/write shape, so the loops
// every caller needs around them (read until full, write until done, copy
// one stream into another) are written once here. One trait per
// capability, composed into the roles generic code asks for:
//
//   Reader          read(buf): File, Pipe, TcpStream, TlsStream, BufReader<R>
//   Writer          write(buf): File, Pipe, TcpStream, TlsStream, BufWriter<W>
//   Seekable        seek(off, whence) / tell(): File
//   Flusher         flush(): File, BufWriter<W>
//   Stream          Reader + Writer: File, Pipe, TcpStream
//   SeekableStream  Stream + Seekable: File
//
//   File      (io_file.h)    f.read / f.write on an open file
//   Pipe      (ipc/pipe.h)   read end / write end of an anonymous pipe
//   TcpStream (http/http.h)  a connected, blocking TCP socket
//   BufReader / BufWriter (io_buf.h)  buffering over any Reader / Writer
//
// Traits are structural: a type conforms by having the methods, so your own
// stream works with read_exact / write_all / copy_stream (and with any
// generic<R: Reader> code) as soon as it defines read or write with these
// signatures. Each call is resolved at compile time (no dispatch at run
// time).
//
//   ?&File in = File::open("in.bin", "rb");
//   struct Pipe p; p.init();
//   ...
//   long long n = copy_stream(f, &p);      // R = File, W = Pipe
//   if (!write_all(&p, hdr[0..16])) { ... }
//
// Not conforming, on purpose:
//   WsConn  (http/websocket.h)  message-based: recv() yields whole frames.
//   TcpConn (net/tcp.h)         a freestanding, non-blocking segment buffer:
//                               read() returning 0 means "nothing yet", not
//                               EOF, so the loops below would misread it.
#include <std/mem.h>

namespace std {

trait Reader {
    // Reads at most buf.len bytes into buf. Returns the count read, 0 at
    // end of stream, -1 on error. May return fewer than buf.len before
    // the end.
    long long read([]unsigned char buf);
}

trait Writer {
    // Writes at most buf.len bytes from buf. Returns the count written
    // (may be fewer), -1 on error.
    long long write([]const unsigned char buf);
}

trait Seekable {
    // Moves to 'offset' from 'whence' (0 = start, 1 = current, 2 = end).
    // 0 on success.
    int seek(long long offset, int whence);
    // The current offset, -1 on error.
    long long tell();
}

trait Flusher {
    // Pushes buffered writes on to where they go. 0 on success.
    int flush();
}

trait Stream : Reader + Writer {}

trait SeekableStream : Stream + Seekable {}

// Fills buf completely, looping over short reads. 1 when all buf.len bytes
// arrived; 0 when the stream ended or failed first (buf then holds what did
// arrive).
generic<R: Reader>
int read_exact(&R r, []unsigned char buf) {
    unsigned long got = 0UL;
    while (got < buf.len) {
        long long k = r.read(buf[got..buf.len]);
        if (k <= 0LL) { return 0; }
        got = got + (unsigned long)k;
    }
    return 1;
}

// Writes all of buf, looping over short writes. 1 on success, 0 if the
// stream failed (or accepted nothing) first.
generic<W: Writer>
int write_all(&W w, []const unsigned char buf) {
    unsigned long sent = 0UL;
    while (sent < buf.len) {
        long long k = w.write(buf[sent..buf.len]);
        if (k <= 0LL) { return 0; }
        sent = sent + (unsigned long)k;
    }
    return 1;
}

// Copies r into w until r's end. Returns the byte count copied, or -1 if a
// read or write failed (what was copied before stays written).
generic<R: Reader, W: Writer>
long long copy_stream(&R r, &W w) {
    unsigned char chunk[4096];
    long long total = 0LL;
    while (1) {
        long long k = r.read(chunk[..]);
        if (k == 0LL) { return total; }
        if (k < 0LL) { return -1LL; }
        if (!write_all(w, chunk[0..(unsigned long)k])) { return -1LL; }
        total = total + k;
    }
    return total;
}

// The stream's length in bytes (its end offset), leaving the position
// where it was; -1 on error.
generic<S: Seekable>
long long stream_size(&S s) {
    long long cur = s.tell();
    if (cur < 0LL) { return -1LL; }
    if (s.seek(0LL, 2) != 0) { return -1LL; }
    long long end = s.tell();
    if (s.seek(cur, 0) != 0) { return -1LL; }
    return end;
}

} // namespace std