~sircmpwn/hare

0df9af142931b1150589e6f8c52e73b2c8092d4c — Drew DeVault a month ago 1809e28
all: introduce io::handle and refactor usage

The goal of this round of refactoring is to simplify the API a bit and
reduce the ABI footprint of io::file (and io::handle).

Signed-off-by: Drew DeVault <sir@cmpwn.com>
75 files changed, 626 insertions(+), 625 deletions(-)

M bufio/buffered.ha
M bufio/memstream.ha
M bufio/scanner.ha
M cmd/harec/context.ha
M cmd/harec/errors.ha
M cmd/harec/gen.ha
M cmd/harec/main.ha
M cmd/harec/qbe.ha
M cmd/haredoc/docstr.ha
M cmd/haredoc/hare.ha
M cmd/haredoc/html.ha
M cmd/haredoc/main.ha
M cmd/haredoc/tty.ha
M compress/flate/inflate.ha
M compress/zlib/.testdata/gen.ha
M compress/zlib/reader.ha
M crypto/random/random.ha
M encoding/base64/README
M encoding/base64/base64.ha
M encoding/hex/hex.ha
M fmt/fmt.ha
M format/ini/scan.ha
M format/ini/types.ha
M format/xml/parser.ha
M format/xml/types.ha
M fs/fs.ha
M fs/mem/+test.ha
M fs/mem/mem.ha
M fs/mem/stream.ha
M fs/types.ha
M getopt/getopts.ha
M hare/lex/README
M hare/lex/lex.ha
M hare/module/manifest.ha
M hare/unparse/decl.ha
M hare/unparse/expr.ha
M hare/unparse/ident.ha
M hare/unparse/import.ha
M hare/unparse/type.ha
M hare/unparse/unit.ha
M hare/unparse/util.ha
M io/+linux/file.ha
M io/+test/stream.ha
A io/README
M io/copy.ha
A io/empty.ha
A io/filestream.ha
A io/handle.ha
M io/limit.ha
M io/stream.ha
M io/tee.ha
M net/+linux.ha
M net/dns/query.ha
M net/ip/ip.ha
M net/tcp/+linux.ha
M net/udp/+linux.ha
M net/unix/+linux.ha
M os/+linux/dirfdfs.ha
M os/+linux/stdfd.ha
D os/stdfd.ha
M scripts/gen-stdlib
M stdlib.mk
M strio/README
M strio/dynamic.ha
M strio/fixed.ha
M strio/ops.ha
M temp/+linux.ha
M unix/hosts/lookup.ha
M unix/passwd/group.ha
M unix/passwd/passwd.ha
M unix/poll/+linux.ha
M unix/resolvconf/load.ha
M unix/tty/+linux/isatty.ha
M unix/tty/+linux/winsize.ha
M uuid/uuid.ha
M bufio/buffered.ha => bufio/buffered.ha +27 -44
@@ 6,7 6,7 @@ use strings;

export type bufstream = struct {
	stream: io::stream,
	source: *io::stream,
	source: io::handle,
	rbuffer: []u8,
	wbuffer: []u8,
	ravail: size,


@@ 31,10 31,10 @@ export type bufstream = struct {
// 	let wbuf: [os::BUFSIZ]u8 = [0...];
// 	let buffered = bufio::buffered(source, rbuf, wbuf);
export fn buffered(
	src: *io::stream,
	src: io::handle,
	rbuf: []u8,
	wbuf: []u8,
) *io::stream = {
) io::handle = {
	let s = alloc(bufstream { ... });
	let st = static_buffered(src, rbuf, wbuf, s);
	st.closer = &buffered_close;


@@ 42,7 42,7 @@ export fn buffered(
};

export fn static_buffered(
	src: *io::stream,
	src: io::handle,
	rbuf: []u8,
	wbuf: []u8,
	s: *bufstream,


@@ 50,9 50,7 @@ export fn static_buffered(
	static let flush_default = ['\n': u32: u8];
	*s = bufstream {
		stream = io::stream {
			name = src.name,
			closer = &buffered_close_static,
			unwrap = &buffered_unwrap,
			...
		},
		source = src,


@@ 74,11 72,14 @@ export fn static_buffered(
	return &s.stream;
};

fn getstream(in: io::handle) *bufstream = {
	assert(isbuffered(in), "Attempted to use bufio on unbuffered stream");
	return in as *io::stream: *bufstream;
};

// Flushes pending writes to the underlying stream.
export fn flush(s: *io::stream) (io::error | void) = {
	assert(s.writer == &buffered_write,
		"bufio::flushed used on non-buffered stream");
	let s = s: *bufstream;
export fn flush(s: io::handle) (io::error | void) = {
	let s = getstream(s);
	if (s.wavail == 0) {
		return;
	};


@@ 89,48 90,36 @@ export fn flush(s: *io::stream) (io::error | void) = {

// Sets the list of bytes which will cause the stream to flush when written. By
// default, the stream will flush when a newline (\n) is written.
export fn setflush(s: *io::stream, b: []u8) void = {
	assert(s.writer == &buffered_write,
		"bufio: setflush used on non-buffered stream");
	let s = s: *bufstream;
export fn setflush(s: io::handle, b: []u8) void = {
	let s = getstream(s);
	s.flush = b;
};

// Returns true if this is a buffered stream.
export fn isbuffered(s: *io::stream) bool = {
	return s.reader == &buffered_read || s.writer == &buffered_write;
};

// Returns true if this stream or any underlying streams are buffered.
export fn any_isbuffered(s: *io::stream) bool = {
	for (!isbuffered(s)) {
		s = match (io::source(s)) {
		case errors::unsupported =>
			return false;
		case s: *io::stream  =>
			yield s;
		};
	};
	return true;
};

// Unreads a slice of bytes. The next read calls on this buffer will consume the
// un-read data before consuming further data from the underlying source, or any
// buffered data.
export fn unread(s: *io::stream, buf: []u8) void = {
	assert(isbuffered(s), "bufio: unread used on non-buffered stream");
	let s = s: *bufstream;
export fn unread(s: io::handle, buf: []u8) void = {
	let s = getstream(s);
	append(s.unread, buf...);
};

// Unreads a rune; see [[unread]].
export fn unreadrune(s: *io::stream, rn: rune) void = {
	assert(isbuffered(s), "bufio: unread used on non-buffered stream");
	let s = s: *bufstream;
export fn unreadrune(s: io::handle, rn: rune) void = {
	let s = getstream(s);
	const buf = utf8::encoderune(rn);
	insert(s.unread[0], buf...);
};

// Returns true if an [[io::handle]] is a [[buffered]] stream.
export fn isbuffered(in: io::handle) bool = {
	match (in) {
	case io::file =>
		return false;
	case st: *io::stream =>
		return st.reader == &buffered_read || st.writer == &buffered_write;
	};
};

fn buffered_close(s: *io::stream) void = {
	assert(s.closer == &buffered_close);
	if (s.writer != null) {


@@ 146,12 135,6 @@ fn buffered_close_static(s: *io::stream) void = {
	};
};

fn buffered_unwrap(s: *io::stream) *io::stream = {
	assert(s.unwrap == &buffered_unwrap);
	let s = s: *bufstream;
	return s.source;
};

fn buffered_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
	assert(s.reader == &buffered_read);
	let s = s: *bufstream;

M bufio/memstream.ha => bufio/memstream.ha +28 -33
@@ 15,7 15,6 @@ type memstream = struct {
export fn fixed(in: []u8, mode: io::mode) *io::stream = {
	let s = alloc(memstream {
		stream = io::stream {
			name = "<bufio::fixed>",
			closer = &fixed_close,
			...
		},


@@ 65,7 64,6 @@ export fn dynamic(mode: io::mode) *io::stream = dynamic_from([], mode);
export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = {
	let s = alloc(memstream {
		stream = io::stream {
			name = "<bufio::dynamic>",
			closer = &dynamic_close,
			seeker = &seek,
			...


@@ 82,62 80,59 @@ export fn dynamic_from(in: []u8, mode: io::mode) *io::stream = {
	return s;
};

fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = {
	let s = s: *memstream;
	insert(s.buf[s.pos], buf...);
	s.pos += len(buf);
	return len(buf);
};

fn dynamic_close(s: *io::stream) void = {
	const s = s: *memstream;
	free(s.buf);
	free(s);
fn getmemstream(in: io::handle) *memstream = {
	match (in) {
	case io::file =>
		abort("Invalid use of bufio with unbuffered file");
	case st: *io::stream =>
		assert(st.closer == &dynamic_close);
		return st: *memstream;
	};
};

// Closes the stream without freeing the dynamic buffer, instead transferring
// ownership of it to the caller.
export fn finish(s: *io::stream) []u8 = {
	if (s.closer != &dynamic_close) {
		abort("bufio::finish called on non-bufio::dynamic stream");
	};
	let s = s: *memstream;
export fn finish(in: io::handle) []u8 = {
	let s = getmemstream(in);
	let buf = s.buf;
	free(s);
	return buf;
};

// Returns the current buffer.
export fn buffer(s: *io::stream) []u8 = {
	if (s.closer != &dynamic_close) {
		abort("bufio::buffer called on non-bufio::dynamic stream");
	};
	let s = s: *memstream;
export fn buffer(in: io::handle) []u8 = {
	const s = getmemstream(in);
	return s.buf;
};

// Resets the dynamic buffer's length to zero, but keeps the allocated memory
// around for future writes.
export fn reset(s: *io::stream) void = {
	if (s.writer != &dynamic_write) {
		abort("bufio::reset called on non-bufio::dynamic stream");
	};
	const s = s: *memstream;
export fn reset(in: io::handle) void = {
	const s = getmemstream(in);
	s.pos = 0;
	s.buf = s.buf[..0];
};

// Truncates the dynamic buffer, freeing memory associated with it and setting
// its length to zero.
export fn truncate(s: *io::stream) (void | errors::unsupported) = {
	if (s.writer != &dynamic_write) {
		return errors::unsupported;
	};
	let s = s: *memstream;
export fn truncate(in: io::handle) (void | errors::unsupported) = {
	let s = getmemstream(in);
	s.pos = 0;
	delete(s.buf[..]);
};

fn dynamic_write(s: *io::stream, buf: const []u8) (size | io::error) = {
	let s = s: *memstream;
	insert(s.buf[s.pos], buf...);
	s.pos += len(buf);
	return len(buf);
};

fn dynamic_close(s: *io::stream) void = {
	const s = s: *memstream;
	free(s.buf);
	free(s);
};

fn read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
	let s = s: *memstream;

M bufio/scanner.ha => bufio/scanner.ha +12 -10
@@ 4,11 4,11 @@ use io;
use strings;
use types;

// Reads a single byte from the stream.
export fn scanbyte(stream: *io::stream) (u8 | io::EOF | io::error) = {
// Reads a single byte from an [[io::handle]].
export fn scanbyte(file: io::handle) (u8 | io::EOF | io::error) = {
	let buf: [1]u8 = [0...];

	match (io::read(stream, buf)?) {
	match (io::read(file, buf)?) {
	case read: size =>
		if (read > 0) {
			return buf[0];


@@ 22,11 22,11 @@ export fn scanbyte(stream: *io::stream) (u8 | io::EOF | io::error) = {

// Reads a slice of bytes until the delimiter. Delimiter is not included. The
// return value must be freed by the caller.
export fn scantok(stream: *io::stream, delim: u8...) ([]u8 | io::EOF | io::error) = {
export fn scantok(file: io::handle, delim: u8...) ([]u8 | io::EOF | io::error) = {
	let buf: []u8 = [];

	for (true) {
		match (scanbyte(stream)?) {
		match (scanbyte(file)?) {
		case res: u8 =>
			if (bytes::contains(delim, res)) {
				break;


@@ 45,13 45,15 @@ export fn scantok(stream: *io::stream, delim: u8...) ([]u8 | io::EOF | io::error

// Reads a slice of bytes until a newline character (\n, 0x10). Newline itself
// is not included. The return value must be freed by the caller.
export fn scanline(stream: *io::stream) ([]u8 | io::EOF | io::error) =
	scantok(stream, '\n': u32: u8);
export fn scanline(file: io::handle) ([]u8 | io::EOF | io::error) =
	scantok(file, '\n': u32: u8);

// Reads a rune from a UTF-8 stream.
export fn scanrune(stream: *io::stream) (rune | utf8::invalid | io::EOF | io::error) = {
export fn scanrune(
	file: io::handle,
) (rune | utf8::invalid | io::EOF | io::error) = {
	let b: [4]u8 = [0...];
	match (io::read(stream, b[..1])?) {
	match (io::read(file, b[..1])?) {
	case n: size =>
		assert(n == 1);
	case io::EOF =>


@@ 67,7 69,7 @@ export fn scanrune(stream: *io::stream) (rune | utf8::invalid | io::EOF | io::er
		return b[0]: u32: rune;
	};

	match (io::read(stream, b[1..sz])?) {
	match (io::read(file, b[1..sz])?) {
	case n: size =>
		assert(n == sz - 1);
	case io::EOF =>

M cmd/harec/context.ha => cmd/harec/context.ha +2 -2
@@ 3,8 3,8 @@ use hare::types;
use hare::unit;

type context = struct {
	out: *io::stream,
	buf: *io::stream,
	out: io::handle,
	buf: io::handle,
	store: *types::typestore,
	unit: *unit::unit,
	arch: struct {

M cmd/harec/errors.ha => cmd/harec/errors.ha +3 -3
@@ 19,17 19,17 @@ fn printerr(err: parse::error) void = {
fn printerr_syntax(err: lex::syntax) void = {
	let location = err.0, details = err.1;
	let file = os::open(location.path)!;
	defer io::close(&file);
	defer io::close(file);

	let line = 1u;
	for (line < location.line) {
		let r = bufio::scanrune(&file) as rune;
		let r = bufio::scanrune(file) as rune;
		if (r == '\n') {
			line += 1u;
		};
	};

	let line = bufio::scanline(&file) as []u8;
	let line = bufio::scanline(file) as []u8;
	defer free(line);
	let line = strings::fromutf8(line);
	fmt::errorfln("{}:{},{}: Syntax error: {}",

M cmd/harec/gen.ha => cmd/harec/gen.ha +1 -1
@@ 11,7 11,7 @@ use io;
use os;
use strings;

fn gen(out: *io::stream, store: *types::typestore, unit: *unit::unit) void = {
fn gen(out: io::handle, store: *types::typestore, unit: *unit::unit) void = {
	// TODO: context_init
	let ctx = context {
		out = out,

M cmd/harec/main.ha => cmd/harec/main.ha +3 -4
@@ 26,7 26,6 @@ export fn main() void = {
	defer getopt::finish(&cmd);

	let out = os::stdout;

	for (let i = 0z; i < len(cmd.opts); i += 1) {
		let opt = cmd.opts[i];
		switch (opt.0) {


@@ 36,7 35,7 @@ export fn main() void = {
		case 'o' =>
			out = match (os::create(opt.1, 0o644)) {
			case f: io::file =>
				yield &f;
				yield f;
			case e: fs::error =>
				fmt::fatal(fs::strerror(e));
			};


@@ 67,9 66,9 @@ export fn main() void = {
			fmt::fatal("Error opening {}: {}",
				cmd.args[i], fs::strerror(err));
		};
		defer io::close(&input);
		defer io::close(input);
		static let buf: [os::BUFSIZ]u8 = [0...];
		let bufin = bufio::buffered(&input, buf, []);
		let bufin = bufio::buffered(input, buf, []);
		defer io::close(bufin);

		let lexer = lex::init(bufin, cmd.args[i]);

M cmd/harec/qbe.ha => cmd/harec/qbe.ha +2 -2
@@ 22,7 22,7 @@ const vvoid: value = value {
};

fn emit(
	to: *io::stream,
	to: io::handle,
	out: (qtypeval | void),
	instr: qinstr,
	args: (value | qval | qtype)...


@@ 230,7 230,7 @@ fn emit(
	fmt::fprintln(to)!;
};

fn qval_emit(to: *io::stream, qv: qval) void = {
fn qval_emit(to: io::handle, qv: qval) void = {
	match (qv) {
	case g: global =>
		fmt::fprintf(to, " ${}", g)!;

M cmd/haredoc/docstr.ha => cmd/haredoc/docstr.ha +2 -2
@@ 21,11 21,11 @@ type docstate = enum {
};

type parser = struct {
	src: *io::stream,
	src: io::handle,
	state: docstate,
};

fn parsedoc(in: *io::stream) parser = {
fn parsedoc(in: io::handle) parser = {
	static let buf: [4096]u8 = [0...];
	return parser {
		src = bufio::buffered(in, buf[..], []),

M cmd/haredoc/hare.ha => cmd/haredoc/hare.ha +3 -3
@@ 17,7 17,7 @@ fn emit_hare(ctx: *context) (void | error) = {
	case readme: io::file =>
		first = false;
		for (true) {
			match (bufio::scanline(&readme)?) {
			match (bufio::scanline(readme)?) {
			case io::EOF => break;
			case b: []u8 =>
				fmt::printfln("// {}", strings::fromutf8(b))?;


@@ 87,7 87,7 @@ fn details_hare(ctx: *context, decl: ast::decl) (void | error) = {
};

// Forked from [[hare::unparse]]
fn unparse_hare(out: *io::stream, d: ast::decl) (size | io::error) = {
fn unparse_hare(out: io::handle, d: ast::decl) (size | io::error) = {
	let n = 0z;
	match (d.decl) {
	case g: []ast::decl_global =>


@@ 154,7 154,7 @@ fn unparse_hare(out: *io::stream, d: ast::decl) (size | io::error) = {
};

fn prototype_hare(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::func_type,
) (size | io::error) = {

M cmd/haredoc/html.ha => cmd/haredoc/html.ha +10 -10
@@ 12,9 12,9 @@ use path;
use strings;
use strio;

// Prints a string to an output stream, escaping any of HTML's reserved
// Prints a string to an output handle, escaping any of HTML's reserved
// characters.
fn html_escape(out: *io::stream, in: str) (size | io::error) = {
fn html_escape(out: io::handle, in: str) (size | io::error) = {
	let z = 0z;
	let iter = strings::iter(in);
	for (true) {


@@ 85,7 85,7 @@ fn emit_html(ctx: *context) (void | error) = {
	case void => void;
	case f: io::file =>
		fmt::println("<div class='readme'>")?;
		markup_html(ctx, &f)?;
		markup_html(ctx, f)?;
		fmt::println("</div>")?;
	};



@@ 305,7 305,7 @@ fn htmlref(ctx: *context, ref: ast::ident) (void | io::error) = {
	free(ident);
};

fn markup_html(ctx: *context, in: *io::stream) (void | io::error) = {
fn markup_html(ctx: *context, in: io::handle) (void | io::error) = {
	let parser = parsedoc(in);
	for (true) {
		const tok = match (scandoc(&parser)) {


@@ 352,7 352,7 @@ fn markup_html(ctx: *context, in: *io::stream) (void | io::error) = {
};

// Forked from [[hare::unparse]]
fn unparse_html(out: *io::stream, d: ast::decl) (size | io::error) = {
fn unparse_html(out: io::handle, d: ast::decl) (size | io::error) = {
	let n = 0z;
	match (d.decl) {
	case c: []ast::decl_const =>


@@ 462,7 462,7 @@ fn builtin_type(b: ast::builtin_type) str = {
};

// Forked from [[hare::unparse]].
fn newline(out: *io::stream, indent: size) (size | io::error) = {
fn newline(out: io::handle, indent: size) (size | io::error) = {
	let n = 0z;
	n += fmt::fprint(out, "\n")?;
	for (let i = 0z; i < indent; i += 1) {


@@ 472,7 472,7 @@ fn newline(out: *io::stream, indent: size) (size | io::error) = {
};

fn enum_html(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::enum_type
) (size | io::error) = {


@@ 505,7 505,7 @@ fn enum_html(
};

fn struct_union_html(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::_type,
	brief: bool,


@@ 553,7 553,7 @@ fn struct_union_html(
};

fn type_html(
	out: *io::stream,
	out: io::handle,
	indent: size,
	_type: ast::_type,
	brief: bool,


@@ 668,7 668,7 @@ fn type_html(
};

fn prototype_html(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::func_type,
	brief: bool,

M cmd/haredoc/main.ha => cmd/haredoc/main.ha +8 -4
@@ 32,7 32,11 @@ type context = struct {
};

export fn main() void = {
	let fmt = if (tty::isatty(os::stdout)) format::TTY else format::HARE;
	let fmt =
		if (tty::isatty(os::stdout_file))
			format::TTY
		else
			format::HARE;
	let template = true;
	let show_undocumented = false;
	const help: [_]getopt::help = [


@@ 139,7 143,7 @@ export fn main() void = {
	defer match (readme) {
	case void => void;
	case f: io::file =>
		io::close(&f);
		io::close(f);
	};

	if (decl != "") {


@@ 212,8 216,8 @@ fn scan(path: str) (ast::subunit | error) = {
	case err: fs::error =>
		fmt::fatal("Error reading {}: {}", path, fs::strerror(err));
	};
	defer io::close(&input);
	const lexer = lex::init(&input, path, lex::flags::COMMENTS);
	defer io::close(input);
	const lexer = lex::init(input, path, lex::flags::COMMENTS);
	return parse::subunit(&lexer)?;
};


M cmd/haredoc/tty.ha => cmd/haredoc/tty.ha +5 -5
@@ 17,7 17,7 @@ fn emit_tty(ctx: *context) (void | error) = {

	match (ctx.readme) {
	case readme: io::file =>
		for (true) match (bufio::scanline(&readme)?) {
		for (true) match (bufio::scanline(readme)?) {
		case io::EOF => break;
		case b: []u8 =>
			firstline = false;


@@ 88,7 88,7 @@ fn details_tty(ctx: *context, decl: ast::decl) (void | error) = {
};

// Forked from [[hare::unparse]]
fn unparse_tty(out: *io::stream, d: ast::decl) (size | io::error) = {
fn unparse_tty(out: io::handle, d: ast::decl) (size | io::error) = {
	let n = 0z;
	match (d.decl) {
	case g: []ast::decl_global =>


@@ 163,7 163,7 @@ fn unparse_tty(out: *io::stream, d: ast::decl) (size | io::error) = {
};

fn prototype_tty(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::func_type,
) (size | io::error) = {


@@ 194,7 194,7 @@ fn prototype_tty(

// Forked from [[hare::unparse]]
fn struct_union_type_tty(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::_type,
) (size | io::error) = {


@@ 243,7 243,7 @@ fn struct_union_type_tty(

// Forked from [[hare::unparse]]
fn type_tty(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::_type,
) (size | io::error) = {

M compress/flate/inflate.ha => compress/flate/inflate.ha +3 -3
@@ 36,7 36,7 @@ type inflate_err = enum u8 {

export type decompressor = struct {
	io::stream,
	in: *io::stream,
	in: io::handle,
	bitbuf: u32,
	cnt: u32,
	final: bool,


@@ 77,10 77,10 @@ export type decompressor = struct {
};

// Creates a stream which decompresses Deflate (RFC 1951) data.
export fn inflate(s: *io::stream) decompressor = decompressor {
export fn inflate(src: io::handle) decompressor = decompressor {
	reader = &read,
	closer = &close,
	in = s,
	in = src,
	bitbuf = 0,
	cnt = 0,
	final = false,

M compress/zlib/.testdata/gen.ha => compress/zlib/.testdata/gen.ha +16 -28
@@ 5,16 5,16 @@ use io;
use os;

fn write(name: str, buf: []u8) void = {
	fmt::printfln("const {}: []u8 = [", name);
	fmt::printfln("const {}: []u8 = [", name)!;
	for (let i = 0z; i < len(buf); i += 1) {
		fmt::print("\t");
		fmt::print("\t")!;
		for (let j = 0z; j < 11 && i < len(buf) - 1; j += 1) {
			fmt::printf("0x{:02X}, ", buf[i]);
			fmt::printf("0x{:02X}, ", buf[i])!;
			i += 1;
		};
		fmt::printfln("0x{:02X},", buf[i]);
		fmt::printfln("0x{:02X},", buf[i])!;
	};
	fmt::println("];\n");
	fmt::println("];\n")!;
};

export fn main() void = {


@@ 25,38 25,26 @@ export fn main() void = {
	];

	for (let i = 0z; i < len(vectors); i += 1) {
		let in = match (os::open(vectors[i].0, fs::flags::RDONLY)) {
			s: *io::stream => s,
			e: fs::error => fmt::fatal(fs::strerror(e)),
		};
		const in = os::open(vectors[i].0)!;
		defer io::close(in);
		let ins = bufio::dynamic(io::mode::WRITE);
		match (io::copy(ins, in)) {
			size => void,
			e: io::error => fmt::fatal(io::strerror(e)),
		};
		let inb = bufio::finish(ins);
		const ins = bufio::dynamic(io::mode::WRITE);
		io::copy(ins, in)!;
		const inb = bufio::finish(ins);
		defer free(inb);
		write(vectors[i].0, inb);

		let out = match (os::open(vectors[i].1, fs::flags::RDONLY)) {
			s: *io::stream => s,
			e: fs::error => fmt::fatal(fs::strerror(e)),
		};
		const out = os::open(vectors[i].1)!;
		defer io::close(out);
		let outs = bufio::dynamic(io::mode::WRITE);
		match (io::copy(outs, out)) {
			size => void,
			e: io::error => fmt::fatal(io::strerror(e)),
		};
		let outb = bufio::finish(ins);
		const outs = bufio::dynamic(io::mode::WRITE);
		io::copy(outs, out)!;
		const outb = bufio::finish(ins);
		defer free(outb);
		write(vectors[i].1, outb);
	};

	fmt::printfln("const vectors: [_](*[]u8, *[]u8) = [");
	fmt::printfln("const vectors: [_](*[]u8, *[]u8) = [")!;
	for (let i = 0z; i < len(vectors); i += 1) {
		fmt::printfln("\t(&{}, &{}),", vectors[i].0, vectors[i].1);
		fmt::printfln("\t(&{}, &{}),", vectors[i].0, vectors[i].1)!;
	};
	fmt::println("];");
	fmt::println("];")!;
};

M compress/zlib/reader.ha => compress/zlib/reader.ha +5 -5
@@ 17,7 17,7 @@ def FLEVEL: u8 = 0b11000000;

export type reader = struct {
	io::stream,
	source: *io::stream,
	source: io::handle,
	flate: flate::decompressor,
	hash: adler32::state,
};


@@ 85,10 85,10 @@ fn close(s: *io::stream) void = {
};

// Creates a stream which decompresses zlib (RFC 1950) data.
export fn decompress(s: *io::stream) (reader | io::error) = {
export fn decompress(src: io::handle) (reader | io::error) = {
	let buf: [2]u8 = [0...];
	for (let n = 0z; n < len(buf)) {
		match (io::read(s, buf[n..])?) {
		match (io::read(src, buf[n..])?) {
		case io::EOF =>
			return wraperror(decompress_err::EOF);
		case z: size =>


@@ 115,8 115,8 @@ export fn decompress(s: *io::stream) (reader | io::error) = {
	return reader {
		reader = &read,
		closer = &close,
		source = s,
		flate = flate::inflate(s),
		source = src,
		flate = flate::inflate(src),
		hash = adler32::adler32(),
		...
	};

M crypto/random/random.ha => crypto/random/random.ha +1 -2
@@ 2,12 2,11 @@ use io;
use rt;

let _stream: io::stream = io::stream {
	name = "<random>",
	reader = &rand_reader,
	...
};

// An [[io::stream]] which returns cryptographically random data on reads. Be
// An [[io::handle]] which returns cryptographically random data on reads. Be
// aware, it may return less than you asked for!
export let stream: *io::stream = &_stream;


M encoding/base64/README => encoding/base64/README +2 -2
@@ 1,9 1,9 @@
Implementation of the base 64 encoding as defined by RFC 4648.

There are various functions available for decoding and encoding. The decode
family accepts an [[io::stream]] as input, while the decodeslice and decodestr
family accepts an [[io::handle]] as input, while the decodeslice and decodestr
family of functions accept slices and strings as input, respectively.
[[decode]] accepts an [[io::stream]] for the output, and [[decodeslice]] and
[[decode]] accepts an [[io::handle]] for the output, and [[decodeslice]] and
[[decodestr]] dynamically allocate a slice or string to write the output to, and
return it to the caller (who is then responsible for freeing it). The _static
family of functions, such as [[decode_static]], accept a caller-allocated slice

M encoding/base64/base64.ha => encoding/base64/base64.ha +7 -7
@@ 38,10 38,10 @@ export def PADDING: u8 = '=': u32: u8;
export type invalid = !size;

// Encodes a byte slice using a base 64 encoding alphabet, with padding, and
// writes it to an [[io::stream]]. The number of bytes written is returned.
// writes it to an [[io::handle]]. The number of bytes written is returned.
export fn encode(
	alphabet: []u8,
	sink: *io::stream,
	sink: io::handle,
	b: []u8
) (size | io::error) = {
	let z = 0z;


@@ 101,11 101,11 @@ export fn encodestr(alphabet: []u8, b: []u8) str = {
};

// Decodes base 64-encoded data in the given base 64 alphabet, with padding,
// from an [[io::stream]]. The number of bytes written is returned.
// from an [[io::handle]]. The number of bytes written is returned.
export fn decode(
	alphabet: []u8,
	in: *io::stream,
	out: *io::stream,
	in: io::handle,
	out: io::handle,
) (size | invalid | io::error) = {
	const INVALID_OR_PAD = 255u8;
	let decoder: [256]u8 = [INVALID_OR_PAD...];


@@ 181,11 181,11 @@ export fn decode(
};

// Decodes base 64-encoded data in the given base 64 alphabet, with padding,
// from an [[io::stream]]. The number of bytes written is returned.
// from an [[io::handle]]. The number of bytes written is returned.
export fn decode_static(
	alphabet: []u8,
	out: []u8,
	in: *io::stream,
	in: io::handle,
) (size | invalid) = {
	let buf = bufio::fixed(out, io::mode::WRITE);
	defer io::close(buf);

M encoding/hex/hex.ha => encoding/hex/hex.ha +5 -5
@@ 10,8 10,8 @@ use strio;
// characters.
export type invalid = !void;

// Encodes a byte slice as a hexadecimal string and writes it to a stream.
export fn encode(sink: *io::stream, b: []u8) (size | io::error) = {
// Encodes a byte slice as a hexadecimal string and writes it to an I/O handle.
export fn encode(sink: io::handle, b: []u8) (size | io::error) = {
	let z = 0z;
	for (let i = 0z; i < len(b); i += 1) {
		let s = strconv::u8tosb(b[i], strconv::base::HEX_LOWER);


@@ 67,14 67,14 @@ export fn decode(s: str) ([]u8 | invalid) = {
	decode("this is not hex") as invalid: void;
};

// Outputs a dump of hex data to a stream alongside the offset and an ASCII
// representation (if applicable).
// Outputs a dump of hex data alongside the offset and an ASCII representation
// (if applicable).
//
// Example output:
//
// 	00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
// 	00000010  03 00 3e 00 01 00 00 00  80 70 01 00 00 00 00 00  |..>......p......|
export fn dump(out: *io::stream, data: []u8) (void | io::error) = {
export fn dump(out: io::handle, data: []u8) (void | io::error) = {
	let datalen = len(data): u32;

	for (let off = 0u32; off < datalen; off += 16) {

M fmt/fmt.ha => fmt/fmt.ha +22 -18
@@ 57,14 57,14 @@ export @noreturn fn fatal(fmt: str, args: field...) void = {
	os::exit(1);
};

// Formats text for printing and writes it to an [[io::stream]], followed by a
// Formats text for printing and writes it to an [[io::handle]], followed by a
// line feed.
export fn fprintfln(
	s: *io::stream,
	h: io::handle,
	fmt: str,
	args: field...
) (io::error | size) = {
	return fprintf(s, fmt, args...)? + io::write(s, ['\n': u32: u8])?;
	return fprintf(h, fmt, args...)? + io::write(h, ['\n': u32: u8])?;
};

// Formats values for printing using the default format modifiers and writes


@@ 107,20 107,20 @@ export fn bsprint(buf: []u8, args: formattable...) str = {
};

// Formats values for printing using the default format modifiers and writes
// them to an [[io::stream]] separated by spaces and followed by a line feed.
export fn fprintln(s: *io::stream, args: formattable...) (io::error | size) = {
	return fprint(s, args...)? + io::write(s, ['\n': u32: u8])?;
// them to an [[io::handle]] separated by spaces and followed by a line feed.
export fn fprintln(h: io::handle, args: formattable...) (io::error | size) = {
	return fprint(h, args...)? + io::write(h, ['\n': u32: u8])?;
};

// Formats values for printing using the default format modifiers and writes
// them to an [[io::stream]] separated by spaces.
export fn fprint(s: *io::stream, args: formattable...) (io::error | size) = {
// them to an [[io::handle]] separated by spaces.
export fn fprint(h: io::handle, args: formattable...) (io::error | size) = {
	let mod = modifiers { base = strconv::base::DEC, ... };
	let n = 0z;
	for (let i = 0z; i < len(args); i += 1) {
		n += format(s, args[i], &mod)?;
		n += format(h, args[i], &mod)?;
		if (i != len(args) - 1) {
			n += io::write(s, [' ': u32: u8])?;
			n += io::write(h, [' ': u32: u8])?;
		};
	};
	return n;


@@ 162,9 162,9 @@ type paramindex = (uint | nextparam | void);

type nextparam = void;

// Formats text for printing and writes it to an [[io::stream]].
// Formats text for printing and writes it to an [[io::handle]].
export fn fprintf(
	s: *io::stream,
	h: io::handle,
	fmt: str,
	args: field...
) (io::error | size) = {


@@ 187,7 187,7 @@ export fn fprintf(
			};

			let arg = if (r == '{') {
				n += io::write(s, utf8::encoderune('{'))?;
				n += io::write(h, utf8::encoderune('{'))?;
				continue;
			} else if (ascii::isdigit(r)) {
				strings::push(&iter, r);


@@ 247,7 247,7 @@ export fn fprintf(
				mod.base = strconv::base::DEC;
			};

			n += format(s, arg, mod)?;
			n += format(h, arg, mod)?;
		} else if (r == '}') {
			match (strings::next(&iter)) {
			case void =>


@@ 256,16 256,20 @@ export fn fprintf(
				assert(r == '}', "Invalid format string (hanging '}')");
			};

			n += io::write(s, utf8::encoderune('}'))?;
			n += io::write(h, utf8::encoderune('}'))?;
		} else {
			n += io::write(s, utf8::encoderune(r))?;
			n += io::write(h, utf8::encoderune(r))?;
		};
	};

	return n;
};

fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error) = {
fn format(
	out: io::handle,
	arg: formattable,
	mod: *modifiers,
) (size | io::error) = {
	let z = format_raw(io::empty, arg, mod)?;

	let pad: []u8 = [];


@@ 294,7 298,7 @@ fn format(out: *io::stream, arg: formattable, mod: *modifiers) (size | io::error
};

fn format_raw(
	out: *io::stream,
	out: io::handle,
	arg: formattable,
	mod: *modifiers,
) (size | io::error) = {

M format/ini/scan.ha => format/ini/scan.ha +4 -4
@@ 5,7 5,7 @@ use io;
use strings;

export type scanner = struct {
	in: *io::stream,
	in: io::handle,
	line: str,
	lineno: size,
	section: str,


@@ 13,7 13,7 @@ export type scanner = struct {

// Creates an INI file scanner. Use [[next]] to read entries. The caller must
// call [[finish]] once they're done with this object.
export fn scan(in: *io::stream) scanner = scanner {
export fn scan(in: io::handle) scanner = scanner {
	in = in,
	lineno = 1,
	...


@@ 69,7 69,7 @@ export fn next(sc: *scanner) (entry | io::EOF | error) = {
			case idx: size =>
				yield idx;
			case void =>
				return (sc.in.name, sc.lineno): syntaxerr;
				return sc.lineno: syntaxerr;
			};
			free(sc.section);
			sc.section = strings::dup(strings::sub(line, 1, end));


@@ 80,7 80,7 @@ export fn next(sc: *scanner) (entry | io::EOF | error) = {
		case idx: size =>
			yield idx;
		case void =>
			return (sc.in.name, sc.lineno): syntaxerr;
			return sc.lineno: syntaxerr;
		};
		return (
			sc.section,

M format/ini/types.ha => format/ini/types.ha +2 -2
@@ 3,7 3,7 @@ use fmt;
use io;

// A syntax error occured during parsing.
export type syntaxerr = !(str, size);
export type syntaxerr = !size;

// Any error that may occur during parsing.
export type error = !(io::error | utf8::invalid | syntaxerr);


@@ 17,5 17,5 @@ case utf8::invalid =>
case s: syntaxerr =>
	// XXX: tuple unpacking could improve this
	static let buf: [1024]u8 = [0...];
	yield fmt::bsprintf(buf, "{}:{}: Invalid syntax", s.0, s.1);
	yield fmt::bsprintf(buf, "{}:{}: Invalid syntax", s: size);
};

M format/xml/parser.ha => format/xml/parser.ha +8 -7
@@ 10,20 10,20 @@ use strconv;
use strings;
use strio;

// Returns an XML parser which reads from a stream. The caller must call
// [[parser_free]] when they are finished with it.
// Creates an XML parser. The caller must call [[parser_free]] when they are
// finished with it.
//
// Hare's XML parser only supports UTF-8 encoded input files.
//
// This function will attempt to read the XML prologue before returning, and
// will return an error if it is not valid.
export fn parse(in: *io::stream) (*parser | error) = {
export fn parse(in: io::handle) (*parser | error) = {
	// XXX: The main reason we allocate this instead of returning it on the
	// stack is so that we have a consistent address for the bufio buffer.
	// This is kind of lame, maybe we can avoid that.
	let par = alloc(parser {
		orig = in,
		in = in,
		close = false,
		namebuf = strio::dynamic(),
		entbuf = strio::dynamic(),
		textbuf = strio::dynamic(),


@@ 31,15 31,16 @@ export fn parse(in: *io::stream) (*parser | error) = {
	});
	if (!bufio::isbuffered(in)) {
		par.in = bufio::buffered(par.in, par.buf[..], []);
		par.close = true;
	};
	prolog(par)?;
	return par;
};

// Frees the resources associated with this parser. Does not close the
// underlying stream.
// underlying I/O handle.
export fn parser_free(par: *parser) void = {
	if (par.in != par.orig) {
	if (par.close) {
		io::close(par.in);
	};
	io::close(par.namebuf);


@@ 358,7 359,7 @@ fn scan_namedent(par: *parser) (rune | error) = {
	return syntaxerr;
};

fn scan_name(par: *parser, buf: *io::stream) (str | error) = {
fn scan_name(par: *parser, buf: io::handle) (str | error) = {
	strio::reset(buf);

	const rn = match (bufio::scanrune(par.in)?) {

M format/xml/types.ha => format/xml/types.ha +5 -5
@@ 3,16 3,16 @@ use io;
use os;

export type parser = struct {
	orig: *io::stream,
	in: *io::stream,
	in: io::handle,
	buf: [os::BUFSIZ]u8,
	close: bool,
	state: state,
	tags: []str,

	// strio buffers:
	namebuf: *io::stream,
	entbuf: *io::stream,
	textbuf: *io::stream,
	namebuf: io::handle,
	entbuf: io::handle,
	textbuf: io::handle,
};

export type state = enum {

M fs/fs.ha => fs/fs.ha +2 -2
@@ 13,7 13,7 @@ export fn close(fs: *fs) void = {

// Opens a file. If no flags are provided, the default read/write mode is
// RDONLY.
export fn open(fs: *fs, path: str, flags: flags...) (*io::stream | error) = {
export fn open(fs: *fs, path: str, flags: flags...) (io::handle | error) = {
	match (fs.open) {
	case null =>
		return errors::unsupported;


@@ 43,7 43,7 @@ export fn create(
	path: str,
	mode: mode,
	flags: flags...
) (*io::stream | error) = {
) (io::handle | error) = {
	match (fs.create) {
	case null =>
		return errors::unsupported;

M fs/mem/+test.ha => fs/mem/+test.ha +10 -12
@@ 13,8 13,7 @@ use strconv;

	// fs::create, fs::stat
	for (let i = 0z; i < 6; i += 1) {
		let f = fs::create(memfs, names[i], 0, fs::flags::RDWR);
		let f = f as *io::stream;
		let f = fs::create(memfs, names[i], 0, fs::flags::RDWR)!;
		io::write(f, input[i..])!;
		io::close(f);
		let st = fs::stat(memfs,  names[i]) as fs::filestat;


@@ 23,8 22,7 @@ use strconv;
		assert(st.mode & fs::mode::REG == fs::mode::REG);
	};

	let f = fs::open(memfs, filename, fs::flags::WRONLY, fs::flags::APPEND);
	let f = f as *io::stream;
	let f = fs::open(memfs, filename, fs::flags::WRONLY, fs::flags::APPEND)!;
	io::write(f, input)!;
	io::close(f);
	let st = fs::stat(memfs,  filename) as fs::filestat;


@@ 41,8 39,8 @@ use strconv;
	fs::remove(memfs, "nonexistent")
		as fs::error as errors::noentry: void;

	let f = fs::open(memfs, filename, fs::flags::RDONLY) as *io::stream;
	let f2 = fs::open(memfs, filename, fs::flags::RDONLY) as *io::stream;
	let f = fs::open(memfs, filename, fs::flags::RDONLY)!;
	let f2 = fs::open(memfs, filename, fs::flags::RDONLY)!;
	let output: [12]u8 = [0...];
	assert(io::seek(f2, 3, io::whence::SET) as io::off == 3: io::off);
	assert(io::read(f2, output) as size == 9);


@@ 79,10 77,10 @@ use strconv;
	fs::rmdir(memfs, "") as fs::error as errors::invalid: void;

	fs::mkdir(memfs, "dir") as void;
	f = fs::create(memfs, "dir/file", 0, fs::flags::WRONLY) as *io::stream;
	f = fs::create(memfs, "dir/file", 0, fs::flags::WRONLY)!;
	assert(io::write(f, input[..]) as size == 6);
	io::close(f);
	f = fs::open(memfs, "dir/file", fs::flags::RDONLY) as *io::stream;
	f = fs::open(memfs, "dir/file", fs::flags::RDONLY)!;
	assert(io::read(f, output) as size == 6);
	assert(bytes::equal(input, output[..6]));
	io::close(f);


@@ 96,7 94,7 @@ use strconv;

	let sub = mksubdir(memfs, "dir") as *fs::fs;

	let f = fs::create(sub, "file", 0, fs::flags::WRONLY) as *io::stream;
	let f = fs::create(sub, "file", 0, fs::flags::WRONLY)!;
	io::write(f, [42])!;
	io::close(f);



@@ 104,7 102,7 @@ use strconv;
	assert(sub2 == sub);
	fs::close(sub);

	let f = fs::open(sub2, "file", fs::flags::RDONLY) as *io::stream;
	let f = fs::open(sub2, "file", fs::flags::RDONLY)!;
	assert(io::read(f, output) as size == 1);
	assert(output[0] == 42);
	io::close(f);


@@ 126,8 124,8 @@ use strconv;
	let limit = 32z;
	let memfs = memopen();
	for (let i = 0z; i < limit; i += 1) {
		let f = fs::create(memfs, strconv::ztos(i), 0, fs::flags::RDWR);
		io::close(f as *io::stream);
		let f = fs::create(memfs, strconv::ztos(i), 0, fs::flags::RDWR)!;
		io::close(f);
	};
	let ino = memfs: *inode;
	let dir = ino.data as directory;

M fs/mem/mem.ha => fs/mem/mem.ha +4 -4
@@ 94,7 94,7 @@ fn create(
	path: str,
	mode: fs::mode,
	flags: fs::flags...,
) (*io::stream | fs::error) = {
) (io::handle | fs::error) = {
	let t = file_flags(flags...)?;
	let mode = t.0, appnd = t.1;



@@ 116,21 116,21 @@ fn create(
		parent = parent,
	});
	inode_insert(parent, ino);
	return stream_open(ino, mode, appnd);
	return stream_open(ino, mode, appnd)?;
};

fn open(
	fs: *fs::fs,
	path: str,
	flags: fs::flags...,
) (*io::stream | fs::error) = {
) (io::handle | fs::error) = {
	let t = file_flags(flags...)?;
	let mode = t.0, appnd = t.1;
	let ino = inode_find(fs: *inode, path)?;
	if (ino.data is directory) {
		return fs::wrongtype;
	};
	return stream_open(ino, mode, appnd);
	return stream_open(ino, mode, appnd)?;
};

fn stat(fs: *fs::fs, path: str) (fs::filestat | fs::error) = {

M fs/mem/stream.ha => fs/mem/stream.ha +0 -1
@@ 19,7 19,6 @@ fn stream_open(
	let f = ino.data as file;
	let s = alloc(stream {
		stream = io::stream {
			name = "<fs::mem::stream>",
			closer = &stream_close,
			seeker = &seek,
			...

M fs/types.ha => fs/types.ha +2 -2
@@ 182,7 182,7 @@ export type openfunc = fn(
	fs: *fs,
	path: str,
	flags: flags...
) (*io::stream | error);
) (io::handle | error);

export type openfilefunc = fn(
	fs: *fs,


@@ 195,7 195,7 @@ export type createfunc = fn(
	path: str,
	mode: mode,
	flags: flags...
) (*io::stream | error);
) (io::handle | error);

export type createfilefunc = fn(
	fs: *fs,

M getopt/getopts.ha => getopt/getopts.ha +18 -18
@@ 182,71 182,71 @@ export fn finish(cmd: *command) void = {
	free(cmd.opts);
};

fn _printusage(s: *io::stream, name: str, indent: bool, help: []help) size = {
	let z = fmt::fprint(s, "Usage:", name) as size;
fn _printusage(out: io::handle, name: str, indent: bool, help: []help) size = {
	let z = fmt::fprint(out, "Usage:", name) as size;

	let started_flags = false;
	for (let i = 0z; i < len(help); i += 1) if (help[i] is flag_help) {
		if (!started_flags) {
			z += fmt::fprint(s, " [-") as size;
			z += fmt::fprint(out, " [-") as size;
			started_flags = true;
		};
		const help = help[i] as flag_help;
		z += fmt::fprint(s, help.0: rune) as size;
		z += fmt::fprint(out, help.0: rune) as size;
	};
	if (started_flags) {
		z += fmt::fprint(s, "]") as size;
		z += fmt::fprint(out, "]") as size;
	};

	for (let i = 0z; i < len(help); i += 1) if (help[i] is parameter_help) {
		const help = help[i] as parameter_help;
		if (indent) {
			z += fmt::fprintf(s, "\n\t") as size;
			z += fmt::fprintf(out, "\n\t") as size;
		};
		z += fmt::fprintf(s, " [-{} <{}>]", help.0: rune, help.1) as size;
		z += fmt::fprintf(out, " [-{} <{}>]", help.0: rune, help.1) as size;
	};
	let first_arg = true;
	for (let i = 1z; i < len(help); i += 1) if (help[i] is cmd_help) {
		if (first_arg) {
			if (indent) {
				z += fmt::fprintf(s, "\n\t") as size;
				z += fmt::fprintf(out, "\n\t") as size;
			};
			first_arg = false;
		};
		z += fmt::fprintf(s, " {}", help[i] as cmd_help: str) as size;
		z += fmt::fprintf(out, " {}", help[i] as cmd_help: str) as size;
	};

	return z + fmt::fprint(s, "\n") as size;
	return z + fmt::fprint(out, "\n") as size;
};

// Prints command usage to the provided stream.
export fn printusage(s: *io::stream, name: str, help: []help) void = {
export fn printusage(out: io::handle, name: str, help: []help) void = {
	let z = _printusage(io::empty, name, false, help);
	_printusage(s, name, if (z > 72) true else false, help);
	_printusage(out, name, if (z > 72) true else false, help);
};

// Prints command help to the provided stream.
export fn printhelp(s: *io::stream, name: str, help: []help) void = {
export fn printhelp(out: io::handle, name: str, help: []help) void = {
	if (help[0] is cmd_help) {
		fmt::fprintfln(s, "{}: {}\n", name, help[0] as cmd_help: str)!;
		fmt::fprintfln(out, "{}: {}\n", name, help[0] as cmd_help: str)!;
	};

	printusage(s, name, help);
	printusage(out, name, help);

	for (let i = 0z; i < len(help); i += 1) match (help[i]) {
	case cmd_help => void;
	case (flag_help | parameter_help) =>
		// Only print this if there are flags to show
		fmt::fprint(s, "\n")!;
		fmt::fprint(out, "\n")!;
		break;
	};

	for (let i = 0z; i < len(help); i += 1) match (help[i]) {
	case cmd_help => void;
	case f: flag_help =>
		fmt::fprintfln(s, "-{}: {}", f.0: rune, f.1)!;
		fmt::fprintfln(out, "-{}: {}", f.0: rune, f.1)!;
	case p: parameter_help =>
		fmt::fprintfln(s, "-{} <{}>: {}", p.0: rune, p.1, p.2)!;
		fmt::fprintfln(out, "-{} <{}>: {}", p.0: rune, p.1, p.2)!;
	};
};


M hare/lex/README => hare/lex/README +1 -1
@@ 1,4 1,4 @@
hare::lex provides a lexer for Hare source code. A lexer takes an [[io::stream]]
hare::lex provides a lexer for Hare source code. A lexer takes an [[io::handle]]
and returns a series of Hare [[token]]s. See the Hare specification for more
details:


M hare/lex/lex.ha => hare/lex/lex.ha +3 -3
@@ 10,7 10,7 @@ use strio;
use types;

export type lexer = struct {
	in: *io::stream,
	in: io::handle,
	path: str,
	loc: (uint, uint),
	un: (token | void),


@@ 47,8 47,8 @@ export fn strerror(err: error) const str = {
	};
};

// Initializes a new lexer for the given input stream. The path is borrowed.
export fn init(in: *io::stream, path: str, flags: flags...) lexer = {
// Initializes a new lexer for the given input. The path is borrowed.
export fn init(in: io::handle, path: str, flags: flags...) lexer = {
	let f = flags::NONE;
	for (let i = 0z; i < len(flags); i += 1) {
		f |= flags[i];

M hare/module/manifest.ha => hare/module/manifest.ha +14 -12
@@ 57,7 57,7 @@ export fn manifest_load(ctx: *context, ident: ast::ident) (manifest | error) = {
		return manifest;
	case err: fs::error =>
		return err;
	case file: *io::stream =>
	case file: io::handle =>
		yield file;
	};
	defer io::close(file);


@@ 282,17 282,19 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {
	defer free(mpath);

	let file = temp::named(ctx.fs, cachedir, io::mode::WRITE, 0o644)?;
	let name = file.1, file = file.0;
	defer {
		fs::remove(ctx.fs, file.name): void;
		io::close(&file);
		fs::remove(ctx.fs, name): void;
		io::close(file);
		free(name);
	};

	let ident = unparse::identstr(manifest.ident);
	defer free(ident);
	fmt::fprintfln(&file, "# {}", ident)?;
	fmt::fprintln(&file, "# This file is an internal Hare implementation detail.")?;
	fmt::fprintln(&file, "# The format is not stable.")?;
	fmt::fprintfln(&file, "version {}", VERSION)?;
	fmt::fprintfln(file, "# {}", ident)?;
	fmt::fprintln(file, "# This file is an internal Hare implementation detail.")?;
	fmt::fprintln(file, "# The format is not stable.")?;
	fmt::fprintfln(file, "version {}", VERSION)?;
	for (let i = 0z; i < len(manifest.inputs); i += 1) {
		const input = manifest.inputs[i];
		let hash = hex::encodestr(input.hash);


@@ 300,7 302,7 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {

		const want = fs::stat_mask::INODE | fs::stat_mask::MTIME;
		assert(input.stat.mask & want == want);
		fmt::fprintfln(&file, "input {} {} {} {}",
		fmt::fprintfln(file, "input {} {} {} {}",
			hash, input.path, input.stat.inode,
			time::unix(input.stat.mtime))?;
	};


@@ 310,19 312,19 @@ export fn manifest_write(ctx: *context, manifest: *manifest) (void | error) = {
		let hash = hex::encodestr(ver.hash);
		defer free(hash);

		fmt::fprintf(&file, "module {}", hash)?;
		fmt::fprintf(file, "module {}", hash)?;

		for (let j = 0z; j < len(ver.inputs); j += 1) {
			let hash = hex::encodestr(ver.inputs[i].hash);
			defer free(hash);

			fmt::fprintf(&file, " {}", hash)?;
			fmt::fprintf(file, " {}", hash)?;
		};

		fmt::fprintln(&file)?;
		fmt::fprintln(file)?;
	};

	fs::move(ctx.fs, file.name, mpath)?;
	fs::move(ctx.fs, name, mpath)?;
};

fn input_finish(in: *input) void = {

M hare/unparse/decl.ha => hare/unparse/decl.ha +1 -1
@@ 4,7 4,7 @@ use hare::ast;
use hare::lex;
use strio;

export fn decl(out: *io::stream, d: ast::decl) (size | io::error) = {
export fn decl(out: io::handle, d: ast::decl) (size | io::error) = {
	let n = 0z;
	if (d.exported) {
		n += fmt::fprint(out, "export ")?;

M hare/unparse/expr.ha => hare/unparse/expr.ha +6 -6
@@ 9,7 9,7 @@ use hare::lex;
// binary operators (e.g. +) is not accounted for, so such expressions may
// produce a different AST if parsed again.
export fn expr(
	out: *io::stream,
	out: io::handle,
	indent: size,
	e: ast::expr
) (size | io::error) = {


@@ 390,7 390,7 @@ export fn expr(
};

fn constant(
	out: *io::stream,
	out: io::handle,
	indent: size,
	e: ast::constant_expr,
) (size | io::error) = {


@@ 439,7 439,7 @@ fn constant(
};

fn struct_constant(
	out: *io::stream,
	out: io::handle,
	indent: size,
	sc: ast::struct_constant,
) (size | io::error) = {


@@ 480,7 480,7 @@ fn struct_constant(
};

fn for_expr(
	out: *io::stream,
	out: io::handle,
	indent: size,
	e: ast::for_expr,
) (size | io::error) = {


@@ 506,7 506,7 @@ fn for_expr(
};

fn switch_expr(
	out: *io::stream,
	out: io::handle,
	indent: size,
	e: ast::switch_expr,
) (size | io::error) = {


@@ 545,7 545,7 @@ fn switch_expr(
};

fn match_expr(
	out: *io::stream,
	out: io::handle,
	indent: size,
	e: ast::match_expr,
) (size | io::error) = {

M hare/unparse/ident.ha => hare/unparse/ident.ha +1 -1
@@ 4,7 4,7 @@ use io;
use strio;

// Unparses an identifier.
export fn ident(out: *io::stream, id: ast::ident) (size | io::error) = {
export fn ident(out: io::handle, id: ast::ident) (size | io::error) = {
	let n = 0z;
	for (let i = 0z; i < len(id); i += 1) {
		n += fmt::fprintf(out, "{}{}", id[i],

M hare/unparse/import.ha => hare/unparse/import.ha +1 -1
@@ 4,7 4,7 @@ use hare::ast;
use strio;

// Unparses an [[ast::import]].
export fn import(out: *io::stream, i: ast::import) (size | io::error) = {
export fn import(out: io::handle, i: ast::import) (size | io::error) = {
	let n = 0z;
	n += fmt::fprint(out, "use ")?;
	match (i) {

M hare/unparse/type.ha => hare/unparse/type.ha +3 -3
@@ 52,7 52,7 @@ case ast::builtin_type::VOID =>
};

fn prototype(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::func_type,
) (size | io::error) = {


@@ 80,7 80,7 @@ fn prototype(
};

fn struct_union_type(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::_type,
) (size | io::error) = {


@@ 127,7 127,7 @@ fn struct_union_type(

// Unparses an [[ast::_type]].
export fn _type(
	out: *io::stream,
	out: io::handle,
	indent: size,
	t: ast::_type,
) (size | io::error) = {

M hare/unparse/unit.ha => hare/unparse/unit.ha +1 -1
@@ 3,7 3,7 @@ use fmt;
use hare::ast;

// Unparses an [[ast::subunit]].
export fn subunit(out: *io::stream, s: ast::subunit) (size | io::error) = {
export fn subunit(out: io::handle, s: ast::subunit) (size | io::error) = {
	let n = 0z;
	for (let i = 0z; i < len(s.imports); i += 1) {
		n += import(out, s.imports[i])?;

M hare/unparse/util.ha => hare/unparse/util.ha +1 -1
@@ 1,7 1,7 @@
use io;
use fmt;

fn newline(out: *io::stream, indent: size) (size | io::error) = {
fn newline(out: io::handle, indent: size) (size | io::error) = {
	let n = 0z;
	n += fmt::fprint(out, "\n")?;
	for (let i = 0z; i < indent; i += 1) {

M io/+linux/file.ha => io/+linux/file.ha +36 -110
@@ 7,73 7,15 @@ use strings;
// [[stream]] in most situations, but there are some APIs which require an
// [[file]] with some OS-level handle backing it - this type is used for such
// APIs.
export type file = struct {
	stream,
	fd: int,
};
export type file = int;

// Opens a Unix file descriptor as a file. This is a low-level interface, to
// open files most programs will use something like [[os::open]]. This function
// is not portable.
export fn fdopen(fd: int, name: str, mode: mode) file = {
	let stream = file {
		name = name,
		closer = &fd_close_static,
		copier = &fd_copy,
		seeker = &fd_seek,
		fd = fd,
		...
	};
	if (mode & mode::READ == mode::READ) {
		stream.reader = &fd_read;
	};
	if (mode & mode::WRITE == mode::WRITE) {
		stream.writer = &fd_write;
	};
	return stream;
};

// Duplicates a [[file]] onto the heap, as if it were opened with [[fdalloc]].
export fn filedup(f: *file) *file = {
	let new = alloc(*f);
	new.closer = &fd_close;
	return new;
};

// Similar to [[fdopen]], but heap-allocates the file. Closing the stream will
// free the associated resources.
export fn fdalloc(fd: int, name: str, mode: mode) *file =
	filedup(&fdopen(fd, strings::dup(name), mode));

// Returns true if a [[stream]] is a file.
export fn is_file(s: *stream) bool = {
	return s.reader == &fd_read
		|| s.writer == &fd_write
		|| s.closer == &fd_close
		|| s.copier == &fd_copy;
};
export fn fdopen(fd: int) file = fd;

// Returns the file descriptor for a given [[file]]. This function is not
// portable.
export fn fd(f: *file) int = f.fd;

// Returns the file descriptor for a given [[stream]], returning void if the
// stream is not backed by a file. This function is not portable.
export fn unwrapfd(s: *stream) (int | void) = {
	for (!is_file(s)) {
		s = match (io::source(s)) {
		case errors::unsupported =>
			return;
		case s: *io::stream =>
			yield s;
		};
	};
	return fd(s: *file);
};

fn fd_read(s: *stream, buf: []u8) (size | EOF | error) = {
	let stream = s: *file;
	match (rt::read(stream.fd, buf: *[*]u8, len(buf))) {
fn fd_read(fd: file, buf: []u8) (size | EOF | error) = {
	match (rt::read(fd, buf: *[*]u8, len(buf))) {
	case err: rt::errno =>
		return errors::errno(err);
	case n: size =>


@@ 86,9 28,8 @@ fn fd_read(s: *stream, buf: []u8) (size | EOF | error) = {
	};
};

fn fd_write(s: *stream, buf: const []u8) (size | error) = {
	let stream = s: *file;
	match (rt::write(stream.fd, buf: *const [*]u8, len(buf))) {
fn fd_write(fd: file, buf: const []u8) (size | error) = {
	match (rt::write(fd, buf: *const [*]u8, len(buf))) {
	case err: rt::errno =>
		return errors::errno(err);
	case n: size =>


@@ 96,61 37,46 @@ fn fd_write(s: *stream, buf: const []u8) (size | error) = {
	};
};

fn fd_close_static(s: *stream) void = {
	let stream = s: *file;
	rt::close(stream.fd)!;
};
fn fd_close(fd: file) void = rt::close(fd)!;

fn fd_close(s: *stream) void = {
	fd_close_static(s);
	free(s);
fn fd_seek(
	fd: file,
	off: off,
	whence: whence,
) (off | error) = {
	match (rt::lseek(fd, off: i64, whence: uint)) {
	case err: rt::errno =>
		return errors::errno(err);
	case n: i64 =>
		return n: off;
	};
};

def SENDFILE_MAX: size = 2147479552z;

fn fd_copy(to: *stream, from: *stream) (size | error) = {
	if (!is_file(from)) {
		return errors::unsupported;
	};

	let to = to: *file, from = from: *file;
fn fd_copy(to: file, from: file) (size | error) = {
	let sum = 0z;
	for (true) {
		let n = match (rt::sendfile(to.fd, from.fd,
				null, SENDFILE_MAX)) {
			case err: rt::errno =>
				switch (err) {
				case rt::EINVAL =>
					if (sum == 0) {
						return errors::unsupported;
					};
					return errors::errno(err);
				case =>
					return errors::errno(err);
				};
			case n: size =>
				yield switch (n) {
				case 0 =>
					break;
				case =>
					yield n;
		let n = match (rt::sendfile(to, from, null, SENDFILE_MAX)) {
		case err: rt::errno =>
			switch (err) {
			case rt::EINVAL =>
				if (sum == 0) {
					return errors::unsupported;
				};
				return errors::errno(err);
			case =>
				return errors::errno(err);
			};
		case n: size =>
			yield switch (n) {
			case 0 =>
				break;
			case =>
				yield n;
			};
		};
		sum += n;
	};
	return sum;
};

fn fd_seek(
	s: *stream,
	off: off,
	whence: whence,
) (off | error) = {
	let stream = s: *file;
	match (rt::lseek(stream.fd, off: i64, whence: uint)) {
	case err: rt::errno =>
		return errors::errno(err);
	case n: i64 =>
		return n: off;
	};
};

M io/+test/stream.ha => io/+test/stream.ha +0 -1
@@ 9,7 9,6 @@ type teststream = struct {
};

fn teststream_open() teststream = teststream {
	name = "teststream",
	reader = &teststream_read,
	writer = &teststream_write,
	...

A io/README => io/README +23 -0
@@ 0,0 1,23 @@
The io module provides input and output (I/O) functionality for Hare programs,
such as reading from or writing to files. The I/O module is not generally
responsible for provisioning the I/O objects themselves; see modules like [[os]]
and [[net]] for this purpose.

I/O operations such as [[read]] or [[write]] accept an I/O handle,
[[io::handle]], to specify the object of the I/O operation. This type is a
tagged union of [[io::file]] and *[[io::stream]]. Most programmers should prefer
to use [[io::handle]] unless they specifically require the special semantics of
one of its subtypes.

The [[io::file]] type provides access to an object, usually a file descriptor,
which is provided by the host operating system. It represents objects such as a
file on disk, an open newtork connection, and so on. The use of [[io::file]] is
generally required when working with host I/O, such as for modules like
[[iobus]] or [[unix::poll]].

The [[io::stream]] type is an abstraction that allows Hare programs to implement
their own I/O objects by providing implementations of [[read]], [[write]], and
other functions, for an [[io::handle]]. Several standard library modules offer
implementations of [[io::stream]] for one reason or another, such as [[bufio]].
Additionally, the io module provides some useful general-purpose I/O streams,
such as [[io::tee]].

M io/copy.ha => io/copy.ha +22 -4
@@ 1,15 1,30 @@
use errors;

// Copies data from one stream into another. Note that this function will never
// return if the source stream is infinite.
export fn copy(dest: *stream, src: *stream) (error | size) = {
// Copies data from one handle into another. Note that this function will never
// return if the source handle is infinite.
export fn copy(dest: handle, src: handle) (error | size) = {
	match (dest) {
	case fd: file =>
		if (src is file) {
			return fd_copy(fd, src as file);
		};
		return copy_fallback(dest, src);
	case st: *stream =>
		if (!(src is *stream)) {
			return copy_fallback(dest, src);
		};
		return copy_streams(st, src as *stream);
	};
};

fn copy_streams(dest: *stream, src: *stream) (error | size) = {
	match (dest.copier) {
		case null => void;
		case c: *copier =>
			match (c(dest, src)) {
			case err: error =>
				match (err) {
				case errors::unsupported => void; // Use fallback
				case errors::unsupported => void;
				case =>
					return err;
				};


@@ 17,7 32,10 @@ export fn copy(dest: *stream, src: *stream) (error | size) = {
				return s;
			};
	};
	return copy_fallback(dest, src);
};

fn copy_fallback(dest: handle, src: handle) (error | size) = {
	let w = 0z;
	static let buf: [4096]u8 = [0...];
	for (true) {

A io/empty.ha => io/empty.ha +12 -0
@@ 0,0 1,12 @@
const _empty: stream = stream {
	reader = &empty_read,
	writer = &empty_write,
	...
};

// A [[stream]] which always reads EOF and discards any writes.
export const empty: *io::stream = &_empty;

fn empty_read(s: *stream, buf: []u8) (size | EOF | error) = EOF;

fn empty_write(s: *stream, buf: const []u8) (size | error) = len(buf);

A io/filestream.ha => io/filestream.ha +52 -0
@@ 0,0 1,52 @@
use errors;

export type filestream = struct {
	stream,
	fd: file,
};

// Creates a [[filestream]] for a [[file]], for compatibility with
// [[stream]]-oriented APIs. Note that this is generally not thought to be
// necessary for most programs; most APIs should accept [[handle]] instead of
// [[stream]] in order to support both file-oriented and stream-oriented I/O.
export fn fdstream(fd: file) filestream = filestream {
	reader = &fdstream_read,
	writer = &fdstream_write,
	closer = &fdstream_close,
	seeker = &fdstream_seek,
	copier = &fdstream_copy,
};

fn fdstream_read(st: *stream, buf: []u8) (size | EOF | error) = {
	const st = st: *filestream;
	assert(st.reader == &fdstream_read);
	return fd_read(st.fd, buf);
};

fn fdstream_write(st: *stream, buf: const []u8) (size | error) = {
	const st = st: *filestream;
	assert(st.writer == &fdstream_write);
	return fd_write(st.fd, buf);
};

fn fdstream_close(st: *stream) void = {
	const st = st: *filestream;
	assert(st.closer == &fdstream_close);
	fd_close(st.fd);
};

fn fdstream_seek(st: *stream, off: off, whence: whence) (off | error) = {
	const st = st: *filestream;
	assert(st.seeker == &fdstream_seek);
	return fd_seek(st.fd, off, whence);
};

fn fdstream_copy(to: *stream, from: *stream) (size | error) = {
	const to = to: *filestream;
	const from = from: *filestream;
	assert(to.copier == &fdstream_copy);
	if (from.copier != &fdstream_copy) {
		return errors::unsupported;
	};
	return fd_copy(to.fd, from.fd);
};

A io/handle.ha => io/handle.ha +57 -0
@@ 0,0 1,57 @@
// TODO: Examine the ABI constraints of [[handle]]. Would it be better to make
// stream an integer representing an internal handle into a stream table? This
// would reduce performance for streams somewhat via the indirect lookup, but
// improve the ABI performance for files.

// An I/O handle is a resource which I/O operations may be performed on. It is
// either a [[stream]], which is a userspace I/O abstraction, or a [[file]],
// which is backed by a resource on the host OS, such as a file descriptor.
export type handle = (file | *stream);

// Reads up to len(buf) bytes from a [[handle]] into the given buffer, returning
// the number of bytes read.
export fn read(h: handle, buf: []u8) (size | EOF | error) = {
	match (h) {
	case fd: file =>
		return fd_read(fd, buf);
	case st: *stream =>
		return st_read(st, buf);
	};
};

// Writes up to len(buf) bytes to the [[handle]] from the given buffer,
// returning the number of bytes written.
export fn write(h: handle, buf: const []u8) (size | error) = {
	match (h) {
	case fd: file =>
		return fd_write(fd, buf);
	case st: *stream =>
		return st_write(st, buf);
	};
};

// Closes a [[handle]]. No further operations against this handle are permitted
// after calling this function.
export fn close(h: handle) void = {
	match (h) {
	case fd: file =>
		fd_close(fd);
	case st: *stream =>
		st_close(st);
	};
};

// Sets the offset within a [[handle]].
export fn seek(h: handle, off: off, w: whence) (off | error) = {
	match (h) {
	case fd: file =>
		return fd_seek(fd, off, w);
	case st: *stream =>
		return st_seek(st, off, w);
	};
};

// Returns the current offset within a [[handle]].
export fn tell(h: handle) (off | error) = {
	return seek(h, 0, whence::CUR);
};

M io/limit.ha => io/limit.ha +2 -9
@@ 2,14 2,12 @@ use strings;

export type limitstream = struct {
	stream,
	source: *stream,
	source: handle,
	limit: size,
};

fn limitstream_create(source: *stream, limit: size) limitstream = {
fn limitstream_create(source: handle, limit: size) limitstream = {
	return limitstream {
		name = source.name,
		unwrap = &limit_unwrap,
		source = source,
		limit = limit,
		...


@@ 53,8 51,3 @@ fn limit_write(s: *stream, buf: const []u8) (size | error) = {
	stream.limit -= len(slice);
	return write(stream.source, slice);
};

fn limit_unwrap(s: *stream) *io::stream = {
	let s = s: *limitstream;
	return s.source;
};

M io/stream.ha => io/stream.ha +9 -50
@@ 1,8 1,9 @@
use errors;

// A stream of bytes which supports some subset of read, write, close, or seek
// operations. To create a custom stream, embed this type as the first member of
// a struct with user-specific data and fill out these fields as appropriate.
// A stream of bytes which supports some subset of read, write, close, seek, or
// copy operations. To create a custom stream, embed this type as the first
// member of a struct with user-specific data and fill out these fields as
// appropriate.
//
//	export type my_stream = struct {
//		io::stream,


@@ 12,7 13,6 @@ use errors;
//	export fn open(path: str) my_stream = {
//		let fd = // ...
//		return my_stream {
//			name   = strings::dup(path),
//			reader = &my_stream_read,
//			writer = &my_stream_write,
//			closer = null,


@@ 24,18 24,14 @@ use errors;
//	let stream = open("example");
//	io::read(&stream, buf)!;
export type stream = struct {
	name:   str,
	reader: nullable *reader,
	writer: nullable *writer,
	closer: nullable *closer,
	copier: nullable *copier,
	seeker: nullable *seeker,
	unwrap: nullable *unwrap,
	copier: nullable *copier,
};

// Reads up to len(buf) bytes from the reader into the given buffer, returning
// the number of bytes read.
export fn read(s: *stream, buf: []u8) (size | EOF | error) = {
fn st_read(s: *stream, buf: []u8) (size | EOF | error) = {
	match (s.reader) {
	case null =>
		return errors::unsupported;


@@ 44,9 40,7 @@ export fn read(s: *stream, buf: []u8) (size | EOF | error) = {
	};
};

// Writes up to len(buf) bytes to the stream from the given buffer, returning
// the number of bytes written.
export fn write(s: *stream, buf: const []u8) (size | error) = {
fn st_write(s: *stream, buf: const []u8) (size | error) = {
	match (s.writer) {
	case null =>
		return errors::unsupported;


@@ 55,8 49,7 @@ export fn write(s: *stream, buf: const []u8) (size | error) = {
	};
};

// Closes the stream.
export fn close(s: *stream) void = {
fn st_close(s: *stream) void = {
	match (s.closer) {
	case null => void;
	case c: *closer =>


@@ 64,8 57,7 @@ export fn close(s: *stream) void = {
	};
};

// Sets the offset within the stream.
export fn seek(s: *stream, off: off, w: whence) (off | error) = {
fn st_seek(s: *stream, off: off, w: whence) (off | error) = {
	match (s.seeker) {
	case null =>
		return errors::unsupported;


@@ 73,36 65,3 @@ export fn seek(s: *stream, off: off, w: whence) (off | error) = {
		return sk(s, off, w);
	};
};

// Returns the current offset within the stream.
export fn tell(s: *stream) (off | error) = {
	match (s.seeker) {
	case null =>
		return errors::unsupported;
	case sk: *seeker =>
		return sk(s, 0, whence::CUR);
	};
};

// Returns the underlying stream for a stream which wraps another stream.
export fn source(s: *stream) (*io::stream | errors::unsupported) = {
	match (s.unwrap) {
	case null =>
		return errors::unsupported;
	case uw: *unwrap =>
		return uw(s);
	};
};

let _empty: io::stream = io::stream {
	reader = &empty_read,
	writer = &empty_write,
	...
};

// A [[stream]] which always reads EOF and discards any writes.
export let empty: *io::stream = &_empty;

fn empty_read(s: *stream, buf: []u8) (size | EOF | error) = EOF;

fn empty_write(s: *stream, buf: const []u8) (size | error) = len(buf);

M io/tee.ha => io/tee.ha +3 -10
@@ 1,16 1,15 @@
export type teestream = struct {
	stream,
	source: *stream,
	sink: *stream,
	source: handle,
	sink: handle,
};

// Creates a reader which copies reads into a sink before forwarding them to the
// caller. This stream does not need to be closed, and closing it will not close
// the secondary streams.
export fn tee(source: *stream, sink: *stream) teestream = {
export fn tee(source: handle, sink: handle) teestream = {
	return teestream {
		reader = &tee_read,
		unwrap = &tee_unwrap,
		source = source,
		sink = sink,
		...


@@ 30,9 29,3 @@ fn tee_read(s: *stream, buf: []u8) (size | EOF | error) = {
	};
	return z;
};

fn tee_unwrap(s: *stream) *io::stream = {
	assert(s.unwrap == &tee_unwrap);
	let s = s: *teestream;
	return s.source;
};

M net/+linux.ha => net/+linux.ha +1 -5
@@ 30,11 30,7 @@ export fn stream_accept(l: *listener) (io::file | error) = {
	case fd: int =>
		yield fd;
	};

	static let namebuf: [32]u8 = [0...];
	return io::fdopen(fd,
		fmt::bsprintf(namebuf, "<network fd {}>", fd),
		io::mode::READ | io::mode::WRITE);
	return io::fdopen(fd);
};

export fn stream_shutdown(l: *listener) void = {

M net/dns/query.ha => net/dns/query.ha +8 -8
@@ 27,17 27,17 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = {
	};

	let socket4 = udp::listen(ip::ANY_V4, 0)?;
	defer io::close(&socket4);
	defer io::close(socket4);
	let socket6 = udp::listen(ip::ANY_V6, 0)?;
	defer io::close(&socket6);
	defer io::close(socket6);
	const pollfd: [_]poll::pollfd = [
		poll::pollfd {
			fd = io::fd(&socket4),
			fd = socket4,
			events = poll::event::POLLIN,
			...
		},
		poll::pollfd {
			fd = io::fd(&socket6),
			fd = socket6,
			events = poll::event::POLLIN,
			...
		},


@@ 50,9 50,9 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = {
	// first one which sends us a reasonable answer.
	for (let i = 0z; i < len(servers); i += 1) match (servers[i]) {
	case ip::addr4 =>
		udp::sendto(&socket4, sendbuf[..z], servers[i], 53)?;
		udp::sendto(socket4, sendbuf[..z], servers[i], 53)?;
	case ip::addr6 =>
		udp::sendto(&socket6, sendbuf[..z], servers[i], 53)?;
		udp::sendto(socket6, sendbuf[..z], servers[i], 53)?;
	};

	let header = header { ... };


@@ 65,10 65,10 @@ export fn query(query: *message, servers: ip::addr...) (*message | error) = {

		let src: ip::addr = ip::ANY_V4;
		if (pollfd[0].revents & poll::event::POLLIN != 0) {
			z = udp::recvfrom(&socket4, recvbuf, &src, null)?;
			z = udp::recvfrom(socket4, recvbuf, &src, null)?;
		};
		if (pollfd[1].revents & poll::event::POLLIN != 0) {
			z = udp::recvfrom(&socket6, recvbuf, &src, null)?;
			z = udp::recvfrom(socket6, recvbuf, &src, null)?;
		};

		let expected = false;

M net/ip/ip.ha => net/ip/ip.ha +5 -5
@@ 151,7 151,7 @@ export fn parse(s: str) (addr | invalid) = {
	return invalid;
};

fn fmtv4(s: *io::stream, a: addr4) (io::error | size) = {
fn fmtv4(s: io::handle, a: addr4) (io::error | size) = {
	let ret = 0z;
	for (let i = 0; i < 4; i += 1) {
		if (i > 0) {


@@ 162,7 162,7 @@ fn fmtv4(s: *io::stream, a: addr4) (io::error | size) = {
	return ret;
};

fn fmtv6(s: *io::stream, a: addr6) (io::error | size) = {
fn fmtv6(s: io::handle, a: addr6) (io::error | size) = {
	let ret = 0z;
	let zstart: int = -1;
	let zend: int = -1;


@@ 283,7 283,7 @@ fn masklen(addr: []u8) (void | size) = {
	return n;
};

fn fmtmask(s: *io::stream, mask: addr) (io::error | size) = {
fn fmtmask(s: io::handle, mask: addr) (io::error | size) = {
	let ret = 0z;
	let slice = match (mask) {
	case v4: addr4 =>


@@ 305,7 305,7 @@ fn fmtmask(s: *io::stream, mask: addr) (io::error | size) = {
	return ret;
};

fn fmtsubnet(s: *io::stream, subnet: subnet) (io::error | size) = {
fn fmtsubnet(s: io::handle, subnet: subnet) (io::error | size) = {
	let ret = 0z;
	ret += fmt(s, subnet.addr)?;
	ret += fmt::fprintf(s, "/")?;


@@ 314,7 314,7 @@ fn fmtsubnet(s: *io::stream, subnet: subnet) (io::error | size) = {
};

// Formats an [[addr]] or [[subnet]] and prints it to a stream.
export fn fmt(s: *io::stream, item: (...addr | subnet)) (io::error | size) = {
export fn fmt(s: io::handle, item: (...addr | subnet)) (io::error | size) = {
	match (item) {
	case v4: addr4 =>
		return fmtv4(s, v4)?;

M net/tcp/+linux.ha => net/tcp/+linux.ha +3 -5
@@ 38,8 38,7 @@ export fn connect(
		return errors::errno(err);
	case int => void;
	};
	return io::fdopen(sockfd, ip::string(addr),
		io::mode::READ | io::mode::WRITE);
	return io::fdopen(sockfd);
};

// Binds a TCP listener to the given address.


@@ 119,11 118,10 @@ export fn listen(

// Returns the remote address for a given connection, or void if none is
// available.
export fn peeraddr(peer: *io::file) ((ip::addr, u16) | void) = {
	let fd = io::fd(peer);
export fn peeraddr(peer: io::file) ((ip::addr, u16) | void) = {
	let sn = rt::sockaddr {...};
	let sz = size(rt::sockaddr): u32;
	if (rt::getpeername(fd, &sn, &sz) is rt::errno) {
	if (rt::getpeername(peer, &sn, &sz) is rt::errno) {
		return;
	};
	return ip::from_native(sn);

M net/udp/+linux.ha => net/udp/+linux.ha +9 -12
@@ 27,8 27,7 @@ export fn connect(
	const sz = size(rt::sockaddr): u32;
	match (rt::connect(sockfd, &sockaddr, sz)) {
	case int =>
		return io::fdopen(sockfd, "<udp connection>",
			io::mode::READ | io::mode::WRITE);
		return io::fdopen(sockfd);
	case err: rt::errno =>
		return errors::errno(err);
	};


@@ 76,13 75,12 @@ export fn listen(
		*options[i] = addr.1;
	};

	return io::fdopen(sockfd, "<udp listener>",
		io::mode::READ | io::mode::WRITE);
	return io::fdopen(sockfd);
};

// Sends a UDP packet to the destination previously specified by [[connect]].
export fn send(sock: *io::file, buf: []u8) (size | net::error) = {
	match (rt::send(io::fd(sock), buf: *[*]u8, len(buf), 0)) {
export fn send(sock: io::file, buf: []u8) (size | net::error) = {
	match (rt::send(sock, buf: *[*]u8, len(buf), 0)) {
	case sz: size =>
		return sz;
	case err: rt::errno =>


@@ 92,15 90,14 @@ export fn send(sock: *io::file, buf: []u8) (size | net::error) = {

// Sends a UDP packet using this socket.
export fn sendto(
	sock: *io::file,
	sock: io::file,
	buf: []u8,
	dest: ip::addr,
	port: u16,
) (size | net::error) = {
	const sockaddr = ip::to_native(dest, port);
	const sz = size(rt::sockaddr): u32;
	match (rt::sendto(io::fd(sock), buf: *[*]u8, len(buf),
			0, &sockaddr, sz)) {
	match (rt::sendto(sock, buf: *[*]u8, len(buf), 0, &sockaddr, sz)) {
	case sz: size =>
		return sz;
	case err: rt::errno =>


@@ 110,15 107,15 @@ export fn sendto(

// Receives a UDP packet from a bound socket.
export fn recvfrom(
	sock: *io::file,
	sock: io::file,
	buf: []u8,
	src: nullable *ip::addr,
	port: nullable *u16,
) (size | net::error) = {
	let addrsz = size(rt::sockaddr): u32;
	const sockaddr = rt::sockaddr { ... };
	const sz = match (rt::recvfrom(io::fd(sock), buf: *[*]u8, len(buf),
			0, &sockaddr, &addrsz)) {
	const sz = match (rt::recvfrom(sock, buf: *[*]u8, len(buf), 0,
		&sockaddr, &addrsz)) {
	case sz: size =>
		yield sz;
	case err: rt::errno =>

M net/unix/+linux.ha => net/unix/+linux.ha +1 -3
@@ 32,9 32,7 @@ export fn connect(addr: addr) (io::file | net::error) = {
	case int => void;
	};
	static let buf: [rt::UNIX_PATH_MAX + 32]u8 = [0...];
	return io::fdopen(sockfd,
		fmt::bsprintf(buf, "<unix connection {}>", addr),
		io::mode::READ | io::mode::WRITE);
	return io::fdopen(sockfd);
};

// Binds a UNIX socket listener to the given path.

M os/+linux/dirfdfs.ha => os/+linux/dirfdfs.ha +6 -6
@@ 51,7 51,7 @@ type os_filesystem = struct {
// a dirfdfs, [[fs::flags::NOCTTY]] and [[fs::flags::CLOEXEC]] are used when opening
// the file. If you pass your own flags, it is recommended that you add these
// unless you know that you do not want them.
export fn dirfdopen(fd: int, resolve: resolve_flags...) *fs::fs = {
export fn dirfdopen(fd: io::file, resolve: resolve_flags...) *fs::fs = {
	let ofs = alloc(os_filesystem { ... });
	let fs = static_dirfdopen(fd, ofs);
	for (let i = 0z; i < len(resolve); i += 1) {


@@ 61,7 61,7 @@ export fn dirfdopen(fd: int, resolve: resolve_flags...) *fs::fs = {
	return fs;
};

fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = {
fn static_dirfdopen(fd: io::file, filesystem: *os_filesystem) *fs::fs = {
	*filesystem = os_filesystem {
		fs = fs::fs {
			open = &fs_open,


@@ 158,7 158,7 @@ fn _fs_open(
		yield fd;
	};

	return io::fdopen(fd, path, mode);
	return io::fdopen(fd);
};

fn fs_open_file(


@@ 202,7 202,7 @@ fn fs_open(
	fs: *fs::fs,
	path: str,
	flags: fs::flags...
) (*io::stream | fs::error) = io::filedup(&fs_open_file(fs, path, flags...)?);
) (io::handle | fs::error) = fs_open_file(fs, path, flags...)?;

fn fs_create_file(
	fs: *fs::fs,


@@ 243,8 243,8 @@ fn fs_create(
	path: str,
	mode: fs::mode,
	flags: fs::flags...
) (*io::stream | fs::error) = {
	return io::filedup(&fs_create_file(fs, path, mode, flags...)?);
) (io::handle | fs::error) = {
	return fs_create_file(fs, path, mode, flags...)?;
};

fn fs_remove(fs: *fs::fs, path: str) (void | fs::error) = {

M os/+linux/stdfd.ha => os/+linux/stdfd.ha +25 -14
@@ 1,29 1,40 @@
use bufio;
use io;

let static_stdin_fd: io::file = io::file { ... };
let static_stdout_fd: io::file = io::file { ... };
let static_stderr_fd: io::file = io::file { ... };
let static_stdin_bufio: bufio::bufstream = bufio::bufstream {
	source = 0,
	...
};

let static_stdout_bufio: bufio::bufstream = bufio::bufstream {
	source = 1,
	...
};

// The standard input. This handle is buffered.
export let stdin: io::handle = 0;

let static_stdin_bufio: bufio::bufstream  = bufio::bufstream { ... };
let static_stdout_bufio: bufio::bufstream = bufio::bufstream { ... };
// The standard input, as an [[io::file]]. This handle is unbuffered.
export let stdin_file: io::file = 0;

// The standard output. This handle is buffered.
export let stdout: io::handle = 1;

// The standard output, as an [[io::file]]. This handle is unbuffered.
export let stdout_file: io::file = 1;

// The standard error.
export let stderr: io::file = 2;

// The recommended buffer size for reading from disk.
export def BUFSIZ: size = 4096; // 4 KiB

@init fn init_stdfd() void = {
	static_stdin_fd = io::fdopen(0, "<stdin>", io::mode::READ);
	static_stdout_fd = io::fdopen(1, "<stdout>", io::mode::WRITE);
	static_stderr_fd = io::fdopen(2, "<stderr>", io::mode::WRITE);
	stdin = &static_stdin_fd;
	stdout = &static_stdout_fd;
	stderr = &static_stderr_fd;

	static let stdinbuf: [BUFSIZ]u8 = [0...];
	stdin = bufio::static_buffered(stdin, stdinbuf, [], &static_stdin_bufio);
	stdin = bufio::static_buffered(0, stdinbuf, [], &static_stdin_bufio);

	static let stdoutbuf: [BUFSIZ]u8 = [0...];
	stdout = bufio::static_buffered(stdout, [], stdoutbuf, &static_stdout_bufio);
	stdout = bufio::static_buffered(1, [], stdoutbuf, &static_stdout_bufio);
};

@fini fn fini_stdfd() void = {

D os/stdfd.ha => os/stdfd.ha +0 -10
@@ 1,10 0,0 @@
use io;

// The standard input.
export let stdin: *io::stream = null: *io::stream;

// The standard output.
export let stdout: *io::stream = null: *io::stream;

// The standard error.
export let stderr: *io::stream = null: *io::stream;

M scripts/gen-stdlib => scripts/gen-stdlib +4 -2
@@ 547,6 547,9 @@ gensrcs_io() {
		'arch$(ARCH).ha' \
		copy.ha \
		drain.ha \
		empty.ha \
		filestream.ha \
		handle.ha \
		limit.ha \
		'println$(PLATFORM).ha' \
		stream.ha \


@@ 706,7 709,6 @@ os() {
		'$(PLATFORM)/dirfdfs.ha' \
		'$(PLATFORM)/stdfd.ha' \
		'$(PLATFORM)/fs.ha' \
		stdfd.ha \
		fs.ha
	gen_ssa os io strings types fs encoding::utf8 bytes bufio errors
}


@@ 817,7 819,7 @@ time() {
temp() {
	gen_srcs temp \
		'$(PLATFORM).ha'
	gen_ssa temp crypto::random encoding::hex fs io os path strio fmt
	gen_ssa temp crypto::random encoding::hex fs io os path strio fmt strings
}

gensrcs_types() {

M stdlib.mk => stdlib.mk +8 -4
@@ 821,6 821,9 @@ stdlib_io_srcs= \
	$(STDLIB)/io/arch$(ARCH).ha \
	$(STDLIB)/io/copy.ha \
	$(STDLIB)/io/drain.ha \
	$(STDLIB)/io/empty.ha \
	$(STDLIB)/io/filestream.ha \
	$(STDLIB)/io/handle.ha \
	$(STDLIB)/io/limit.ha \
	$(STDLIB)/io/println$(PLATFORM).ha \
	$(STDLIB)/io/stream.ha \


@@ 1005,7 1008,6 @@ stdlib_os_srcs= \
	$(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \
	$(STDLIB)/os/$(PLATFORM)/stdfd.ha \
	$(STDLIB)/os/$(PLATFORM)/fs.ha \
	$(STDLIB)/os/stdfd.ha \
	$(STDLIB)/os/fs.ha

$(HARECACHE)/os/os.ssa: $(stdlib_os_srcs) $(stdlib_rt) $(stdlib_io) $(stdlib_strings) $(stdlib_types) $(stdlib_fs) $(stdlib_encoding_utf8) $(stdlib_bytes) $(stdlib_bufio) $(stdlib_errors)


@@ 1117,7 1119,7 @@ $(HARECACHE)/strio/strio.ssa: $(stdlib_strio_srcs) $(stdlib_rt) $(stdlib_io) $(s
stdlib_temp_srcs= \
	$(STDLIB)/temp/$(PLATFORM).ha

$(HARECACHE)/temp/temp.ssa: $(stdlib_temp_srcs) $(stdlib_rt) $(stdlib_crypto_random) $(stdlib_encoding_hex) $(stdlib_fs) $(stdlib_io) $(stdlib_os) $(stdlib_path) $(stdlib_strio) $(stdlib_fmt)
$(HARECACHE)/temp/temp.ssa: $(stdlib_temp_srcs) $(stdlib_rt) $(stdlib_crypto_random) $(stdlib_encoding_hex) $(stdlib_fs) $(stdlib_io) $(stdlib_os) $(stdlib_path) $(stdlib_strio) $(stdlib_fmt) $(stdlib_strings)
	@printf 'HAREC \t$@\n'
	@mkdir -p $(HARECACHE)/temp
	@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ntemp \


@@ 2072,6 2074,9 @@ testlib_io_srcs= \
	$(STDLIB)/io/arch$(ARCH).ha \
	$(STDLIB)/io/copy.ha \
	$(STDLIB)/io/drain.ha \
	$(STDLIB)/io/empty.ha \
	$(STDLIB)/io/filestream.ha \
	$(STDLIB)/io/handle.ha \
	$(STDLIB)/io/limit.ha \
	$(STDLIB)/io/println$(PLATFORM).ha \
	$(STDLIB)/io/stream.ha \


@@ 2261,7 2266,6 @@ testlib_os_srcs= \
	$(STDLIB)/os/$(PLATFORM)/dirfdfs.ha \
	$(STDLIB)/os/$(PLATFORM)/stdfd.ha \
	$(STDLIB)/os/$(PLATFORM)/fs.ha \
	$(STDLIB)/os/stdfd.ha \
	$(STDLIB)/os/fs.ha

$(TESTCACHE)/os/os.ssa: $(testlib_os_srcs) $(testlib_rt) $(testlib_io) $(testlib_strings) $(testlib_types) $(testlib_fs) $(testlib_encoding_utf8) $(testlib_bytes) $(testlib_bufio) $(testlib_errors)


@@ 2376,7 2380,7 @@ $(TESTCACHE)/strio/strio.ssa: $(testlib_strio_srcs) $(testlib_rt) $(testlib_io) 
testlib_temp_srcs= \
	$(STDLIB)/temp/$(PLATFORM).ha

$(TESTCACHE)/temp/temp.ssa: $(testlib_temp_srcs) $(testlib_rt) $(testlib_crypto_random) $(testlib_encoding_hex) $(testlib_fs) $(testlib_io) $(testlib_os) $(testlib_path) $(testlib_strio) $(testlib_fmt)
$(TESTCACHE)/temp/temp.ssa: $(testlib_temp_srcs) $(testlib_rt) $(testlib_crypto_random) $(testlib_encoding_hex) $(testlib_fs) $(testlib_io) $(testlib_os) $(testlib_path) $(testlib_strio) $(testlib_fmt) $(testlib_strings)
	@printf 'HAREC \t$@\n'
	@mkdir -p $(TESTCACHE)/temp
	@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ntemp \

M strio/README => strio/README +1 -1
@@ 1,5 1,5 @@
strio provides string-related I/O operations, specifically to make efficient use
of memory while building strings incrementally. The main entry points to strio
are [[dynamic]] and [[fixed]]. Each of the utility functions (e.g.
[[appendrune]]) work correctly with any [[io::stream]], but for efficiency
[[appendrune]]) work correctly with any [[io::handle]], but for efficiency
reasons it is recommended that they are either a strio or [[bufio]] stream.

M strio/dynamic.ha => strio/dynamic.ha +18 -14
@@ 13,10 13,9 @@ type dynamic_stream = struct {
// Calling [[io::close]] on this stream will free the buffer. Call [[strio::finish]]
// instead to free up resources associated with the stream, but transfer
// ownership of the buffer to the caller.
export fn dynamic() *io::stream = {
export fn dynamic() io::handle = {
	let s = alloc(dynamic_stream {
		stream = io::stream {
			name = "<strio::dynamic>",
			writer = &dynamic_write,
			closer = &dynamic_close,
			...


@@ 26,12 25,21 @@ export fn dynamic() *io::stream = {
	return &s.stream;
};

fn get_dynamic_stream(in: io::handle) *dynamic_stream = {
	match (in) {
	case io::file =>
		abort("Invalid use of strio on io::file");
	case st: *io::stream =>
		assert(st.writer == &dynamic_write,
			"Invalid use of strio on non-strio I/O stream");
		return st: *dynamic_stream;
	};
};

// Closes the stream without freeing the buffer, instead transferring ownership
// of it to the caller.
export fn finish(s: *io::stream) str = {
	assert(s.writer == &dynamic_write,
		"strio::finish called on non-strio::dynamic stream");
	let s = s: *dynamic_stream;
export fn finish(in: io::handle) str = {
	let s = get_dynamic_stream(in);
	let buf = s.buf;
	free(s);
	return strings::fromutf8(buf);


@@ 39,19 47,15 @@ export fn finish(s: *io::stream) str = {

// Resets the buffer's length to zero, but keeps the allocated memory around for
// future writes.
export fn reset(s: *io::stream) void = {
	assert(s.writer == &dynamic_write,
		"strio::reset called on non-strio::dynamic stream");
	const s = s: *dynamic_stream;
export fn reset(in: io::handle) void = {
	const s = get_dynamic_stream(in);
	s.buf = s.buf[..0];
};

// Truncates the buffer, freeing memory associated with it and setting its
// length to zero.
export fn truncate(s: *io::stream) void = {
	assert(s.writer == &dynamic_write,
		"strio::truncate called on non-strio::dynamic stream");
	let s = s: *dynamic_stream;
export fn truncate(in: io::handle) void = {
	let s = get_dynamic_stream(in);
	delete(s.buf[..]);
};


M strio/fixed.ha => strio/fixed.ha +14 -9
@@ 9,10 9,9 @@ type fixed_stream = struct {

// Creates a write-only string stream using the provided buffer for storage.
// The program aborts if writes would exceed the buffer's capacity.
export fn fixed(in: []u8) *io::stream = {
export fn fixed(in: []u8) io::handle = {
	let s = alloc(fixed_stream {
		stream = io::stream {
			name = "<strio::fixed>",
			writer = &fixed_write,
			closer = &fixed_close,
			...


@@ 25,15 24,21 @@ export fn fixed(in: []u8) *io::stream = {

// Returns the current contents of the buffer as a string. Aborts the program if
// invalid UTF-8 has been written to the buffer.
export fn string(s: *io::stream) str = {
	if (s.writer == &fixed_write) {
		let stream = s: *fixed_stream;
export fn string(in: io::handle) str = {
	let stream = match (in) {
	case io::file =>
		abort("Invalid use of strio with io::file");
	case st: *io::stream =>
		yield st;
	};

	if (stream.writer == &fixed_write) {
		let stream = stream: *fixed_stream;
		const n = len(stream.buf) - len(stream.cur);
		return strings::fromutf8(stream.buf[..n]);
	} else if (s.writer == &dynamic_write) {
		let s = s: *dynamic_stream;
		let buf = s.buf;
		return strings::fromutf8(buf);
	} else if (stream.writer == &dynamic_write) {
		let stream = stream: *dynamic_stream;
		return strings::fromutf8(stream.buf);
	} else {
		abort("strio::string called on non-strio stream");
	};

M strio/ops.ha => strio/ops.ha +16 -16
@@ 2,16 2,16 @@ use encoding::utf8;
use io;
use strings;

// Appends zero or more strings to an [[io::stream]]. The stream needn't be a
// Appends zero or more strings to an [[io::handle]]. The output needn't be a
// strio stream, but it's generally more efficient if it is. Returns the number
// of bytes written, or an error.
export fn concat(st: *io::stream, strs: str...) (size | io::error) = {
export fn concat(out: io::handle, strs: str...) (size | io::error) = {
	let n = 0z;
	for (let i = 0z; i < len(strs); i += 1) {
		let q = 0z;
		let buf = strings::toutf8(strs[i]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			let w = io::write(out, buf[q..])?;
			n += w;
			q -= w;
		};


@@ 27,24 27,24 @@ export fn concat(st: *io::stream, strs: str...) (size | io::error) = {
	assert(string(st) == "hello world");
};

// Joins several strings together by a delimiter and writes them to a stream.
// The stream needn't be a strio stream, but it's generally more efficient if it
// is.  Returns the number of bytes written, or an error.
export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
// Joins several strings together by a delimiter and writes them to a handle.
// The output needn't be a strio stream, but it's generally more efficient if it
// is. Returns the number of bytes written, or an error.
export fn join(out: io::handle, delim: str, strs: str...) (size | io::error) = {
	let n = 0z;
	let delim = strings::toutf8(delim);
	for (let i = 0z; i < len(strs); i += 1) {
		let q = 0z;
		let buf = strings::toutf8(strs[i]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			let w = io::write(out, buf[q..])?;
			n += w;
			q -= w;
		};
		if (i + 1 < len(strs)) {
			let q = 0z;
			for (q < len(delim)) {
				let w = io::write(st, delim[q..])?;
				let w = io::write(out, delim[q..])?;
				n += w;
				q -= w;
			};


@@ 66,24 66,24 @@ export fn join(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
	assert(string(st) == "foo");
};

// Joins several strings together by a delimiter and writes them to a stream, in
// reverse order. The stream needn't be a strio stream, but it's generally more
// Joins several strings together by a delimiter and writes them to a handle, in
// reverse order. The output needn't be a strio stream, but it's generally more
// efficient if it is. Returns the number of bytes written, or an error.
export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = {
export fn rjoin(out: io::handle, delim: str, strs: str...) (size | io::error) = {
	let n = 0z;
	let delim = strings::toutf8(delim);
	for (let i = len(strs); i > 0; i -= 1) {
		let q = 0z;
		let buf = strings::toutf8(strs[i - 1]);
		for (q < len(buf)) {
			let w = io::write(st, buf[q..])?;
			let w = io::write(out, buf[q..])?;
			n += w;
			q -= w;
		};
		if (i - 1 > 0) {
			let q = 0z;
			for (q < len(delim)) {
				let w = io::write(st, delim[q..])?;
				let w = io::write(out, delim[q..])?;
				n += w;
				q -= w;
			};


@@ 106,5 106,5 @@ export fn rjoin(st: *io::stream, delim: str, strs: str...) (size | io::error) = 
};

// Appends a rune to a stream.
export fn appendrune(st: *io::stream, r: rune) (size | io::error) =
	io::write(st, utf8::encoderune(r));
export fn appendrune(out: io::handle, r: rune) (size | io::error) =
	io::write(out, utf8::encoderune(r));

M temp/+linux.ha => temp/+linux.ha +8 -6
@@ 7,6 7,7 @@ use io;
use os;
use path;
use strio;
use strings;

fn get_tmpdir() str = os::tryenv("TMPDIR", "/tmp");



@@ 35,16 36,17 @@ export fn file(
	// TODO: Add a custom "close" function which removes the named file
	match (os::create(get_tmpdir(), fmode, oflags)) {
	case err: fs::error =>
		return named(os::cwd, get_tmpdir(), iomode, mode...);
		let file = named(os::cwd, get_tmpdir(), iomode, mode...)?;
		free(file.1);
		return file.0;
	case f: io::file =>
		return f;
	};
};

// Creates a named temporary file in the given directory of the given
// filesystem. The path to the temporary file may be found via the name field of
// [[io::stream]]. The caller is responsible for removing the file when they're
// done with it.
// filesystem. The caller is responsible for removing the file and freeing the
// name when they're done with it.
//
// The I/O mode must be either [[io::mode::WRITE]] or [[io::mode::RDWR]].
//


@@ 55,7 57,7 @@ export fn named(
	path: str,
	iomode: io::mode,
	mode: fs::mode...
) (io::file | fs::error) = {
) ((io::file, str) | fs::error) = {
	assert(iomode == io::mode::WRITE || iomode == io::mode::RDWR);
	assert(len(mode) == 0 || len(mode) == 1);



@@ 80,7 82,7 @@ export fn named(
		case err: fs::error =>
			return err;
		case f: io::file =>
			return f;
			return (f, strings::dup(fpath));
		};
	};
	abort(); // Unreachable

M unix/hosts/lookup.ha => unix/hosts/lookup.ha +2 -2
@@ 13,11 13,11 @@ export fn lookup(name: str) []ip::addr = {
	// XXX: Would be cool if we could do this without allocating anything
	// XXX: Would be cool to have meaningful error handling(?)
	const file = os::open(path)!;
	defer io::close(&file);
	defer io::close(file);

	let addrs: []ip::addr = [];
	for (true) {
		const line = match (bufio::scanline(&file)) {
		const line = match (bufio::scanline(file)) {
		case io::EOF =>
			break;
		case line: []u8 =>

M unix/passwd/group.ha => unix/passwd/group.ha +6 -6
@@ 16,10 16,10 @@ export type grent = struct {
	userlist: []str,
};

// Reads a Unix-like group entry from a stream. The caller must free the result
// using [[grent_finish]].
export fn nextgr(stream: *io::stream) (grent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(stream)?) {
// Reads a Unix-like group entry from an [[io::handle]]. The caller must free
// the result using [[grent_finish]].
export fn nextgr(in: io::handle) (grent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(in)?) {
	case ln: []u8 =>
		yield ln;
	case io::EOF =>


@@ 72,10 72,10 @@ export fn getgroup(name: str) (grent | void) = {
	case =>
		abort("Unable to open /etc/group");
	};
	defer io::close(&file);
	defer io::close(file);

	for (true) {
		let ent = match (nextgr(&file)) {
		let ent = match (nextgr(file)) {
		case e: grent =>
			yield e;
		case io::EOF =>

M unix/passwd/passwd.ha => unix/passwd/passwd.ha +6 -6
@@ 22,10 22,10 @@ export type pwent = struct {
	shell: str,
};

// Reads a Unix-like password entry from a stream. The caller must free the
// result using [[pwent_finish]].
export fn nextpw(stream: *io::stream) (pwent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(stream)?) {
// Reads a Unix-like password entry from an [[io::handle]]. The caller must free
// the result using [[pwent_finish]].
export fn nextpw(file: io::handle) (pwent | io::EOF | io::error | invalid) = {
	let line = match (bufio::scanline(file)?) {
	case io::EOF =>
		return io::EOF;
	case ln: []u8 =>


@@ 91,10 91,10 @@ export fn getuser(username: str) (pwent | void) = {
	case =>
		abort("Can't open /etc/passwd");
	};
	defer io::close(&file);
	defer io::close(file);

	for (true) {
		let ent = match (nextpw(&file)) {
		let ent = match (nextpw(file)) {
		case e: pwent =>
			yield e;
		case io::EOF =>

M unix/poll/+linux.ha => unix/poll/+linux.ha +3 -3
@@ 1,6 1,7 @@
use errors;
use time;
use io;
use rt;
use time;

// Events bitfield for the events and revents field of [[pollfd]].
export type event = enum i16 {


@@ 13,8 14,7 @@ export type event = enum i16 {

// A single file descriptor to be polled.
export type pollfd = struct {
	// XXX: TODO: Update me to io::file?
	fd: int,
	fd: io::file,
	events: event,
	revents: event,
};

M unix/resolvconf/load.ha => unix/resolvconf/load.ha +2 -2
@@ 23,10 23,10 @@ export fn load() []ip::addr = {
	};

	const file = os::open(path)!;
	defer io::close(&file);
	defer io::close(file);

	for (true) {
		const line = match (bufio::scanline(&file)) {
		const line = match (bufio::scanline(file)) {
		case io::EOF =>
			break;
		case line: []u8 =>

M unix/tty/+linux/isatty.ha => unix/tty/+linux/isatty.ha +1 -7
@@ 3,13 3,7 @@ use io;
use os;

// Returns whether the given stream is connected to a terminal.
export fn isatty(stream: *io::stream) bool = {
	let fd = match (io::unwrapfd(stream)) {
	case void =>
		return false;
	case fd: int =>
		yield fd;
	};
export fn isatty(fd: io::file) bool = {
	let wsz = rt::winsize { ... };
	match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *void)) {
	case e: rt::errno =>

M unix/tty/+linux/winsize.ha => unix/tty/+linux/winsize.ha +3 -9
@@ 3,14 3,8 @@ use io;
use os;
use rt;

// Returns the dimensions of underlying terminal of the stream.
export fn winsize(tty: *io::stream) (ttysize | error) = {
	let fd = match (io::unwrapfd(tty)) {
	case void =>
		return errors::unsupported;
	case fd: int =>
		yield fd;
	};
// Returns the dimensions of underlying terminal for an [[io::file]].
export fn winsize(fd: io::file) (ttysize | error) = {
	let wsz = rt::winsize { ... };
	match (rt::ioctl(fd, rt::TIOCGWINSZ, &wsz: *void)) {
	case e: rt::errno =>


@@ 24,7 18,7 @@ export fn winsize(tty: *io::stream) (ttysize | error) = {
		};
	case int =>
		return ttysize {
			rows    = wsz.ws_row,
			rows = wsz.ws_row,
			columns = wsz.ws_col,
		};
	};

M uuid/uuid.ha => uuid/uuid.ha +6 -6
@@ 50,8 50,8 @@ export fn generate() uuid = {
// Returns true if two UUIDs are equal.
export fn compare(a: uuid, b: uuid) bool = bytes::equal(a, b);

// Encodes a UUID as a string and writes it to a stream.
export fn encode(out: *io::stream, in: uuid) (size | io::error) = {
// Encodes a UUID as a string and writes it to an I/O handle.
export fn encode(out: io::handle, in: uuid) (size | io::error) = {
	let z = 0z;
	for (let i = TIME_LOW; i < TIME_LOW + 4; i += 1) {
		z += fmt::fprintf(out, "{:02x}", in[i])?;


@@ 72,8 72,8 @@ export fn encode(out: *io::stream, in: uuid) (size | io::error) = {
	return z;
};

// Encodes a UUID as a URI and writes it to a stream.
export fn uri(out: *io::stream, in: uuid) (size | io::error) = {
// Encodes a UUID as a URI and writes it to an I/O handle.
export fn uri(out: io::handle, in: uuid) (size | io::error) = {
	return fmt::fprintf(out, "urn:uuid:")? + encode(out, in)?;
};



@@ 105,8 105,8 @@ export fn encodeuri(in: uuid) str = {
	assert(encodestr(in) == "3ded910c-8080-4bc8-af39-b6cccee36741");
};

// Decodes a UUID as a string from an [[io::stream]].
export fn decode(in: *io::stream) (uuid | invalid | io::error) = {
// Decodes a UUID as a string from an [[io::handle]].
export fn decode(in: io::handle) (uuid | invalid | io::error) = {
	let u: uuid = [0...];
	for (let i = 0z; i < len(u); i += 1) {
		let buf: [2]u8 = [0...];