os: graduate tryopen/trywrite/tryread to (T | oserror)
Final piece of the os module graduation: the three try* wrappers move off the (T | str) placeholder shape. `oserror` becomes a real Hare-style error type (`!i64` instead of plain `i64`), so it's picked up by ?-propagation as the error half without callers having to name it. tryread: (i64 | oserror) was (i64 | str) trywrite: (i64 | oserror) was (i64 | str) tryopen: (i32 | oserror) was (i32 | str) Callsites updated: wwdump uses tryopen; the e2e trywrite probe matches on os.oserror and validates -EBADF for a bad fd (-9 instead of the old "write failed" string length). selfhost/test/smoke.ww switched to raw os.open(2) instead of os.tryopen for probe 7 — same reason as the os.readall switch in the prior commit: probe 6 in 990_selfhost compiles smoke.ww standalone, and cross-module type refs like `os.oserror` don't resolve in that mode.
This commit is contained in:
25
lib/os/os.ww
25
lib/os/os.ww
@@ -73,18 +73,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
|||||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
|
||||||
// model, see lib/errors); the sum type makes success/failure explicit
|
// -errno). The sum type makes success/failure explicit and lets
|
||||||
// without overloading length-zero.
|
// callers `?` the result up the stack.
|
||||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = read(fd, buf, n);
|
let r: i64 = read(fd, buf, n);
|
||||||
if (r < 0) { return "read failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = write(fd, buf, n);
|
let r: i64 = write(fd, buf, n);
|
||||||
if (r < 0) { return "write failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,9 +96,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
|||||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||||
let fd: i32 = open(path, flags, mode);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return "open failed"; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
return fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,9 +111,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
|||||||
|
|
||||||
// oserror — the underlying errno from a failed syscall, as a
|
// oserror — the underlying errno from a failed syscall, as a
|
||||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
// `!`-flagged alias makes ?-propagation pick this variant as the
|
||||||
// i64 byte count. Hare's analogue is errors::errno.
|
// error half of any (T | oserror) shape. Hare's analogue is
|
||||||
export type oserror = i64;
|
// errors::errno carried inside io::error.
|
||||||
|
export type oserror = !i64;
|
||||||
|
|
||||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||||
|
|||||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
|||||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
|
||||||
// model, see lib/errors); the sum type makes success/failure explicit
|
// -errno). The sum type makes success/failure explicit and lets
|
||||||
// without overloading length-zero.
|
// callers `?` the result up the stack.
|
||||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = read(fd, buf, n);
|
let r: i64 = read(fd, buf, n);
|
||||||
if (r < 0) { return "read failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = write(fd, buf, n);
|
let r: i64 = write(fd, buf, n);
|
||||||
if (r < 0) { return "write failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
|||||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||||
let fd: i32 = open(path, flags, mode);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return "open failed"; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
return fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
|||||||
|
|
||||||
// oserror — the underlying errno from a failed syscall, as a
|
// oserror — the underlying errno from a failed syscall, as a
|
||||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
// `!`-flagged alias makes ?-propagation pick this variant as the
|
||||||
// i64 byte count. Hare's analogue is errors::errno.
|
// error half of any (T | oserror) shape. Hare's analogue is
|
||||||
export type oserror = i64;
|
// errors::errno carried inside io::error.
|
||||||
|
export type oserror = !i64;
|
||||||
|
|
||||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||||
|
|||||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
|||||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
|
||||||
// model, see lib/errors); the sum type makes success/failure explicit
|
// -errno). The sum type makes success/failure explicit and lets
|
||||||
// without overloading length-zero.
|
// callers `?` the result up the stack.
|
||||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = read(fd, buf, n);
|
let r: i64 = read(fd, buf, n);
|
||||||
if (r < 0) { return "read failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = write(fd, buf, n);
|
let r: i64 = write(fd, buf, n);
|
||||||
if (r < 0) { return "write failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
|||||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||||
let fd: i32 = open(path, flags, mode);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return "open failed"; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
return fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
|||||||
|
|
||||||
// oserror — the underlying errno from a failed syscall, as a
|
// oserror — the underlying errno from a failed syscall, as a
|
||||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
// `!`-flagged alias makes ?-propagation pick this variant as the
|
||||||
// i64 byte count. Hare's analogue is errors::errno.
|
// error half of any (T | oserror) shape. Hare's analogue is
|
||||||
export type oserror = i64;
|
// errors::errno carried inside io::error.
|
||||||
|
export type oserror = !i64;
|
||||||
|
|
||||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||||
@@ -8091,11 +8092,11 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
let fdorerr: (i32 | str) = os.tryopen(path, os.O_RDONLY, 0i32);
|
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||||
let fd: i32 = -1;
|
let fd: i32 = -1;
|
||||||
match (fdorerr) {
|
match (fdorerr) {
|
||||||
case let v: i32 => fd = v;
|
case let v: i32 => fd = v;
|
||||||
case let e: str => {
|
case let e: os.oserror => {
|
||||||
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
||||||
os.write(2, path, argstrlen(path): u64);
|
os.write(2, path, argstrlen(path): u64);
|
||||||
os.write(2, "\n".ptr, 1u64);
|
os.write(2, "\n".ptr, 1u64);
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
|||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
let fdorerr: (i32 | str) = os.tryopen(path, os.O_RDONLY, 0i32);
|
let fdorerr: (i32 | os.oserror) = os.tryopen(path, os.O_RDONLY, 0i32);
|
||||||
let fd: i32 = -1;
|
let fd: i32 = -1;
|
||||||
match (fdorerr) {
|
match (fdorerr) {
|
||||||
case let v: i32 => fd = v;
|
case let v: i32 => fd = v;
|
||||||
case let e: str => {
|
case let e: os.oserror => {
|
||||||
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
os.write(2, "wwdump: cannot open ".ptr, 20u64);
|
||||||
os.write(2, path, argstrlen(path): u64);
|
os.write(2, path, argstrlen(path): u64);
|
||||||
os.write(2, "\n".ptr, 1u64);
|
os.write(2, "\n".ptr, 1u64);
|
||||||
|
|||||||
@@ -74,18 +74,18 @@ export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
|||||||
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallible wrappers. The error variant is a plain str (Plan 9 errstr
|
// Fallible wrappers. The error variant is `oserror` (an i64 carrying
|
||||||
// model, see lib/errors); the sum type makes success/failure explicit
|
// -errno). The sum type makes success/failure explicit and lets
|
||||||
// without overloading length-zero.
|
// callers `?` the result up the stack.
|
||||||
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = read(fd, buf, n);
|
let r: i64 = read(fd, buf, n);
|
||||||
if (r < 0) { return "read failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = {
|
export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let r: i64 = write(fd, buf, n);
|
let r: i64 = write(fd, buf, n);
|
||||||
if (r < 0) { return "write failed"; };
|
if (r < 0) { return r: oserror; };
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,9 +97,9 @@ export fn open(path: *u8, flags: i32, mode: i32) i32 = {
|
|||||||
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = {
|
export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | oserror) = {
|
||||||
let fd: i32 = open(path, flags, mode);
|
let fd: i32 = open(path, flags, mode);
|
||||||
if (fd < 0) { return "open failed"; };
|
if (fd < 0) { return fd: i64: oserror; };
|
||||||
return fd;
|
return fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,9 +112,10 @@ export fn lseek(fd: i32, off: i64, whence: i32) i64 = {
|
|||||||
|
|
||||||
// oserror — the underlying errno from a failed syscall, as a
|
// oserror — the underlying errno from a failed syscall, as a
|
||||||
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
// negative i64 (Linux's int convention; e.g. -2 = ENOENT). The
|
||||||
// NAMED-i64 alias makes it a distinct variant tag from a "good"
|
// `!`-flagged alias makes ?-propagation pick this variant as the
|
||||||
// i64 byte count. Hare's analogue is errors::errno.
|
// error half of any (T | oserror) shape. Hare's analogue is
|
||||||
export type oserror = i64;
|
// errors::errno carried inside io::error.
|
||||||
|
export type oserror = !i64;
|
||||||
|
|
||||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
||||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||||
@@ -572,12 +573,12 @@ export fn main() i32 = {
|
|||||||
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
||||||
// always exists on Linux, no write side, and is non-empty.
|
// always exists on Linux, no write side, and is non-empty.
|
||||||
let path: str = "/proc/self/cmdline";
|
let path: str = "/proc/self/cmdline";
|
||||||
let fd_or_err: (i32 | str) = os.tryopen(path.ptr, os.O_RDONLY, 0i32);
|
// Use raw os.open here (returns i32 with -errno) for the same
|
||||||
let fd: i32 = 0;
|
// reason as os.read below: probe 6 in 990_selfhost compiles
|
||||||
match (fd_or_err) {
|
// smoke.ww standalone (no `use` expansion), so cross-module type
|
||||||
case let v: i32 => fd = v;
|
// references like `os.oserror` don't resolve at that step.
|
||||||
case let e: str => return 21;
|
let fd: i32 = os.open(path.ptr, os.O_RDONLY, 0i32);
|
||||||
};
|
if (fd < 0) { return 21; };
|
||||||
let rbuf: [128]u8;
|
let rbuf: [128]u8;
|
||||||
// Use raw os.read here (single syscall, plain i64) instead of
|
// Use raw os.read here (single syscall, plain i64) instead of
|
||||||
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
||||||
|
|||||||
@@ -152,12 +152,12 @@ export fn main() i32 = {
|
|||||||
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
// Probe 7 — file open/read via the new os APIs. /proc/self/cmdline
|
||||||
// always exists on Linux, no write side, and is non-empty.
|
// always exists on Linux, no write side, and is non-empty.
|
||||||
let path: str = "/proc/self/cmdline";
|
let path: str = "/proc/self/cmdline";
|
||||||
let fd_or_err: (i32 | str) = os.tryopen(path.ptr, os.O_RDONLY, 0i32);
|
// Use raw os.open here (returns i32 with -errno) for the same
|
||||||
let fd: i32 = 0;
|
// reason as os.read below: probe 6 in 990_selfhost compiles
|
||||||
match (fd_or_err) {
|
// smoke.ww standalone (no `use` expansion), so cross-module type
|
||||||
case let v: i32 => fd = v;
|
// references like `os.oserror` don't resolve at that step.
|
||||||
case let e: str => return 21;
|
let fd: i32 = os.open(path.ptr, os.O_RDONLY, 0i32);
|
||||||
};
|
if (fd < 0) { return 21; };
|
||||||
let rbuf: [128]u8;
|
let rbuf: [128]u8;
|
||||||
// Use raw os.read here (single syscall, plain i64) instead of
|
// Use raw os.read here (single syscall, plain i64) instead of
|
||||||
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
// os.readall: the 990 cgen-match probe compiles smoke.ww
|
||||||
|
|||||||
@@ -747,26 +747,26 @@ static const struct row rows[] = {
|
|||||||
" return acc;\n"
|
" return acc;\n"
|
||||||
"};", 19 },
|
"};", 19 },
|
||||||
/* End-to-end stdlib usage: pull in lib/os and exercise the
|
/* End-to-end stdlib usage: pull in lib/os and exercise the
|
||||||
* fallible API tryread/trywrite returning (i64 | str) over a
|
* fallible API tryread/trywrite returning (i64 | oserror) over
|
||||||
* real syscall. Validates that imported tagged-union returns
|
* a real syscall. oserror carries -errno; on a bad fd we expect
|
||||||
* survive the linker as well as the call ABI. */
|
* -EBADF (-9). */
|
||||||
{ "use os;\n"
|
{ "use os;\n"
|
||||||
"fn main() i32 = {\n"
|
"fn main() i32 = {\n"
|
||||||
" let buf: [3]u8;\n"
|
" let buf: [3]u8;\n"
|
||||||
" buf[0] = 88: u8;\n"
|
" buf[0] = 88: u8;\n"
|
||||||
" let ok: (i64 | str) = os.trywrite(1, buf.ptr, 1u64);\n"
|
" let ok: (i64 | os.oserror) = os.trywrite(1, buf.ptr, 1u64);\n"
|
||||||
" let bad: (i64 | str) = os.trywrite(999: i32, buf.ptr, 1u64);\n"
|
" let bad: (i64 | os.oserror) = os.trywrite(999: i32, buf.ptr, 1u64);\n"
|
||||||
" let acc: i32 = 0;\n"
|
" let acc: i32 = 0;\n"
|
||||||
" match (ok) {\n"
|
" match (ok) {\n"
|
||||||
" case let n: i64 => acc += n: i32;\n"
|
" case let n: i64 => acc += n: i32;\n"
|
||||||
" case let e: str => acc += -100;\n"
|
" case let e: os.oserror => acc += -100;\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" match (bad) {\n"
|
" match (bad) {\n"
|
||||||
" case let n: i64 => acc += -100;\n"
|
" case let n: i64 => acc += -100;\n"
|
||||||
" case let e: str => acc += e.len: i32;\n"
|
" case let e: os.oserror => acc += (- (e: i64)): i32;\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" return acc;\n"
|
" return acc;\n"
|
||||||
"};", 13 }, /* 1 byte written to fd 1, plus len(\"write failed\")=12 */
|
"};", 10 }, /* 1 byte written + 9 (EBADF) */
|
||||||
/* strconv.stoi64: fallible signed decimal, graduated to
|
/* strconv.stoi64: fallible signed decimal, graduated to
|
||||||
* (i64 | invalid | overflow). invalid carries the offending
|
* (i64 | invalid | overflow). invalid carries the offending
|
||||||
* index; overflow is the void variant. */
|
* index; overflow is the void variant. */
|
||||||
|
|||||||
Reference in New Issue
Block a user