From 981a2e1a0097ebd2e3237f5693cf8d929c6a8ca9 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:03:07 -0400 Subject: [PATCH 01/36] Add missing `io.Stream_Mode` responses --- core/bytes/buffer.odin | 2 +- core/c/libc/stdio.odin | 2 +- core/os/os2/file_posix.odin | 2 +- core/os/os2/file_windows.odin | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index a7e9b1c64..f46fb826a 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -402,7 +402,7 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse buffer_destroy(b) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 4be00ff0b..019389b0d 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -368,7 +368,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream { return 0, .Empty case .Query: - return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size }) + return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query }) } return } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 96b7ffe4e..26bcb111c 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -446,7 +446,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) case: return 0, .Empty diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 74067464b..284c78320 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -813,7 +813,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = error_to_io_error(ferr) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) } return 0, .Empty } From d66486c17e55261d33b2a3ee9e7c6b68656a9ef2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:39:14 -0400 Subject: [PATCH 02/36] Make `io.Section_Reader` set `base` too This should fix seeking from `.Start`, getting the `size`, and `read_at`. Also make the API consistent with the other `*_init` procs in `util.odin` by returning the `io.Reader`. --- core/io/util.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/io/util.odin b/core/io/util.odin index c24eb99c5..27c344890 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -376,11 +376,12 @@ Section_Reader :: struct { limit: i64, } -section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) { +section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) -> Reader { s.r = r + s.base = off s.off = off s.limit = off + n - return + return section_reader_to_stream(s) } section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) { out.data = s From da49f7eb4e31240f2d83260357a389c6b7a08873 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 02:03:41 -0400 Subject: [PATCH 03/36] Make `bytes.reader_init` return an `io.Stream` Makes the API like the other stream `init` procs. --- core/bytes/reader.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index 4b18345ba..a2e3294f0 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -9,10 +9,11 @@ Reader :: struct { prev_rune: int, // previous reading index of rune or < 0 } -reader_init :: proc(r: ^Reader, s: []byte) { +reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream { r.s = s r.i = 0 r.prev_rune = -1 + return reader_to_stream(r) } reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { From 8251f4d7d009ce5f0e7d9293d70458971dd4c553 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 02:05:05 -0400 Subject: [PATCH 04/36] Return `.EOF` in `bytes.buffer_read_at` instead This is consistent with the other stream `read` procs --- core/bytes/buffer.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index f46fb826a..995068aad 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -249,7 +249,7 @@ buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.E b.last_read = .Invalid if uint(offset) >= len(b.buf) { - err = .Invalid_Offset + err = .EOF return } n = copy(p, b.buf[offset:]) From 521e47ffee6de2efbbae98527cb29e2176cd9869 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:13:50 -0400 Subject: [PATCH 05/36] Don't invalidate `prev_rune` if `Reader` seek failed --- core/bytes/reader.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index a2e3294f0..d85e4fe13 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -98,7 +98,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error { return nil } reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r.prev_rune = -1 abs: i64 switch whence { case .Start: @@ -115,6 +114,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E return 0, .Invalid_Offset } r.i = abs + r.prev_rune = -1 return abs, nil } reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { From 7c6cc8104112bd6e260355f4ba3566ffe0de8809 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 03:15:58 -0400 Subject: [PATCH 06/36] Add `Seek` behavior to `bytes.Buffer` --- core/bytes/buffer.odin | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index 995068aad..4208b0d46 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -310,6 +310,26 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error { return nil } +buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { + abs: i64 + switch whence { + case .Start: + abs = offset + case .Current: + abs = i64(b.off) + offset + case .End: + abs = i64(len(b.buf)) + offset + case: + return 0, .Invalid_Whence + } + + if abs < 0 { + return 0, .Invalid_Offset + } + b.last_read = .Invalid + b.off = int(abs) + return abs, nil +} buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) { i := index_byte(b.buf[b.off:], delim) @@ -395,6 +415,9 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse return io._i64_err(buffer_write(b, p)) case .Write_At: return io._i64_err(buffer_write_at(b, p, int(offset))) + case .Seek: + n, err = buffer_seek(b, offset, whence) + return case .Size: n = i64(buffer_capacity(b)) return @@ -402,7 +425,7 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse buffer_destroy(b) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy, .Query}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query}) } return 0, .Empty } From e83b982afe103544112dea83e1befe1061f9ea93 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 04:03:40 -0400 Subject: [PATCH 07/36] Measure `bytes.Buffer` size by `length` instead of `capacity` --- core/bytes/buffer.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index 4208b0d46..0399beea4 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -419,7 +419,7 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse n, err = buffer_seek(b, offset, whence) return case .Size: - n = i64(buffer_capacity(b)) + n = i64(buffer_length(b)) return case .Destroy: buffer_destroy(b) From 1ced76cdd1078066a3c95f78c5f33d05a3892737 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 04:35:55 -0400 Subject: [PATCH 08/36] Fix broken cases of `Seek` usage in `_file_stream_proc` Handles `EINVAL`, among other fixes. --- core/os/file_windows.odin | 2 ++ core/os/os_windows.odin | 4 ++++ core/os/stream.odin | 8 ++++++++ core/sys/windows/winerror.odin | 1 + 4 files changed, 15 insertions(+) diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 3f6f781aa..2a00b44b1 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -192,6 +192,8 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { case 0: w = win32.FILE_BEGIN case 1: w = win32.FILE_CURRENT case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 273fe5af0..6fb0631cd 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -43,6 +43,7 @@ ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) ERROR_MOD_NOT_FOUND :: _Platform_Error(126) ERROR_PROC_NOT_FOUND :: _Platform_Error(127) +ERROR_NEGATIVE_SEEK :: _Platform_Error(131) ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) ERROR_ALREADY_EXISTS :: _Platform_Error(183) ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) @@ -91,6 +92,9 @@ get_last_error :: proc "contextless" () -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, diff --git a/core/os/stream.odin b/core/os/stream.odin index 8acbee489..61c7c3582 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -47,6 +47,14 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Seek: n, os_err = seek(fd, offset, int(whence)) + if os_err != nil { + switch whence { + case .Start, .Current, .End: + return 0, .Invalid_Offset + case: + return 0, .Invalid_Whence + } + } case .Size: n, os_err = file_size(fd) case .Destroy: diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 8882dad71..d3df3b815 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -213,6 +213,7 @@ ERROR_BROKEN_PIPE : DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED : DWORD : 120 ERROR_INSUFFICIENT_BUFFER : DWORD : 122 ERROR_INVALID_NAME : DWORD : 123 +ERROR_NEGATIVE_SEEK : DWORD : 131 ERROR_BAD_ARGUMENTS : DWORD : 160 ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 From 5c8c63ae045cb975d05a715ecacc7e8c1fbab038 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:19:00 -0400 Subject: [PATCH 09/36] Fix FreeBSD implementations of `read_at` and `write_at` These procedures must not modify the underlying file pointer. --- core/os/os_freebsd.odin | 29 +++++++------ core/sys/freebsd/syscalls.odin | 76 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 41c487b2b..16dcf2473 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -6,6 +6,7 @@ foreign import libc "system:c" import "base:runtime" import "core:strings" import "core:c" +import "core:sys/freebsd" Handle :: distinct i32 File_Time :: distinct u64 @@ -481,23 +482,27 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) + + return bytes_read, cast(_Platform_Error)errno } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) + + return bytes_written, cast(_Platform_Error)errno } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index 4a79bd56c..d1876a5e2 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -14,6 +14,8 @@ import "core:c" // FreeBSD 15 syscall numbers // See: https://alfonsosiciliano.gitlab.io/posts/2023-08-28-freebsd-15-system-calls.html +SYS_read : uintptr : 3 +SYS_write : uintptr : 4 SYS_open : uintptr : 5 SYS_close : uintptr : 6 SYS_getpid : uintptr : 20 @@ -29,12 +31,46 @@ SYS_shutdown : uintptr : 134 SYS_setsockopt : uintptr : 105 SYS_sysctl : uintptr : 202 SYS__umtx_op : uintptr : 454 +SYS_pread : uintptr : 475 +SYS_pwrite : uintptr : 476 SYS_accept4 : uintptr : 541 // // Odin syscall wrappers // +// Read input. +// +// The read() function appeared in Version 1 AT&T UNIX. +read :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_read, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output. +// +// The write() function appeared in Version 1 AT&T UNIX. +write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + // Open or create a file for reading, writing or executing. // // The open() function appeared in Version 1 AT&T UNIX. @@ -469,6 +505,46 @@ _umtx_op :: proc "contextless" (obj: rawptr, op: Userland_Mutex_Operation, val: return cast(Errno)result } +// Read input without modifying the file pointer. +// +// The pread() function appeared in AT&T System V Release 4 UNIX. +pread :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pread, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output without modifying the file pointer. +// +// The pwrite() function appeared in AT&T System V Release 4 UNIX. +// +// BUGS +// +// The pwrite() system call appends the file without changing the file +// offset if O_APPEND is set, contrary to IEEE Std 1003.1-2008 (“POSIX.1”) +// where pwrite() writes into offset regardless of whether O_APPEND is set. +pwrite :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + // Accept a connection on a socket. // // The accept4() system call appeared in FreeBSD 10.0. From 861d892ffe52a51964fc3ea70dd85068335885bc Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:29:23 -0400 Subject: [PATCH 10/36] Make Windows `pread` and `pwrite` conform to POSIX --- core/os/file_windows.odin | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 2a00b44b1..375da6aff 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -225,11 +225,13 @@ file_size :: proc(fd: Handle) -> (i64, Error) { MAX_RW :: 1<<30 @(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -249,11 +251,13 @@ pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { return int(done), e } @(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] - } o := win32.OVERLAPPED{ @@ -273,13 +277,6 @@ pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { /* read_at returns n: 0, err: 0 on EOF -on Windows, read_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - read_at(fd, bytes, 0) - read(fd, bytes) - -will read from the location twice on *nix, and from two different locations on Windows */ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { @@ -304,15 +301,6 @@ read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { return } -/* -on Windows, write_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - write_at(fd, bytes, 0) - write(fd, bytes) - -will write to the location twice on *nix, and to two different locations on Windows -*/ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset From 7061032db6368e2c12c3ffcfd6bf64bef90fe92b Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:41:06 -0400 Subject: [PATCH 11/36] Fix NetBSD implementations of `read_at` and `write_at` These procedures must not modify the underlying file pointer. --- core/os/os_netbsd.odin | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 82a8dc1eb..130848ab0 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -426,7 +426,9 @@ foreign libc { @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- @@ -535,23 +537,31 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { From e1555e04ad77cde909e1f674ae301ea7a61a7aff Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:01:33 -0400 Subject: [PATCH 12/36] Fix Haiku implementations of `read_at` and `write_at` --- core/os/os_haiku.odin | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 2d87c07a6..102ea9c06 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -127,7 +127,9 @@ foreign libc { @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- @@ -250,23 +252,31 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { From 6c4806835c49a92e4156c965b0c2dfa64d119dac Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:13:50 -0400 Subject: [PATCH 13/36] Fix indentation --- core/os/os_haiku.odin | 76 +++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 102ea9c06..2371a909e 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -119,51 +119,51 @@ S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK foreign libc { - @(link_name="_errorp") __error :: proc() -> ^c.int --- + @(link_name="_errorp") __error :: proc() -> ^c.int --- - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } MAXNAMLEN :: haiku.NAME_MAX From d60f404bb4f19d589963450bd2d836ef85f3fb18 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:17:42 -0400 Subject: [PATCH 14/36] Fix OpenBSD implementations of `read_at` and `write_at` --- core/os/os_openbsd.odin | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 44eac8564..cbcdebf8c 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -351,7 +351,9 @@ foreign libc { @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- @@ -463,23 +465,31 @@ write :: proc(fd: Handle, data: []byte) -> (int, Error) { } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = read(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr := seek(fd, offset, SEEK_CUR) or_return - n, err = write(fd, data) - _, err1 := seek(fd, curr, SEEK_SET) - if err1 != nil && err == nil { - err = err1 + if len(data) == 0 { + return 0, nil } - return + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { From eb92a2df7181082c1ab65d29d2e9d4c092a46a5c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:20:43 -0400 Subject: [PATCH 15/36] Fix indentation --- core/os/os_openbsd.odin | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index cbcdebf8c..b1b90b89b 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -343,52 +343,52 @@ AT_REMOVEDIR :: 0x08 @(default_calling_convention="c") foreign libc { - @(link_name="__error") __error :: proc() -> ^c.int --- + @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } @(require_results) From 741ccd7ff549e974458a4630b98d9b82c80ce927 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:39:59 -0400 Subject: [PATCH 16/36] Zero `n` on error in `_file_stream_proc` --- core/os/stream.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/os/stream.odin b/core/os/stream.odin index 61c7c3582..c849319ae 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -66,5 +66,8 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, if err == nil && os_err != nil { err = error_to_io_error(os_err) } + if err != nil { + n = 0 + } return } From 5b9e9fb822ee45f64322d0f10a406d567e6563a8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 19 Aug 2024 04:50:48 -0400 Subject: [PATCH 17/36] Add test suite for `core:io` --- tests/core/io/test_core_io.odin | 549 ++++++++++++++++++++++++++++++++ tests/core/normal.odin | 1 + 2 files changed, 550 insertions(+) create mode 100644 tests/core/io/test_core_io.odin diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin new file mode 100644 index 000000000..1d5df3adb --- /dev/null +++ b/tests/core/io/test_core_io.odin @@ -0,0 +1,549 @@ +package test_core_io + +import "core:bytes" +import "core:io" +import "core:log" +import "core:os" +import "core:strings" +import "core:testing" + +Passed_Tests :: distinct io.Stream_Mode_Set + +_test_stream :: proc( + t: ^testing.T, + stream: io.Stream, + buffer: []u8, + + reading_consumes: bool = false, + resets_on_empty: bool = false, + do_destroy: bool = true, + + loc := #caller_location +) -> (passed: Passed_Tests, ok: bool) { + // We only test what the stream reports to support. + + mode_set := io.query(stream) + + // Can't feature-test anything if Query isn't supported. + testing.expectf(t, .Query in mode_set, "stream does not support .Query: %v", mode_set, loc = loc) or_return + + passed += { .Query } + + size := i64(len(buffer)) + + // Do some basic Seek sanity testing. + if .Seek in mode_set { + pos, err := io.seek(stream, 0, io.Seek_From(-1)) + testing.expectf(t, pos == 0 && err == .Invalid_Whence, + "Seek(-1) didn't fail with Invalid_Whence: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start isn't 0: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Seek Current isn't 0 at the start: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Start) + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek Start-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, -1, .Current) + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek Current-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .End) + testing.expectf(t, pos == size && err == nil, + "Seek End+0 failed: %v != size<%i>, %v", pos, size, err, loc = loc) or_return + + pos, err = io.seek(stream, 0, .Current) + testing.expectf(t, pos == size && err == nil, + "Seek Current isn't size<%v> at the End: %v, %v", size, pos, err, loc = loc) or_return + + // Seeking past the End is accepted throughout the API. + // + // It's _reading_ past the End which is erroneous. + pos, err = io.seek(stream, 1, .End) + testing.expectf(t, pos == size+1 && err == nil, + "Seek End+1 failed: %v, %v", pos, err, loc = loc) or_return + + // Reset our position for future tests. + pos, err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && err == nil, + "Seek Start reset failed: %v, %v", pos, err, loc = loc) or_return + + passed += { .Seek } + } + + // Test Size. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Size reports %v for its size; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Size expected no error: %v", size_err, loc = loc) or_return + + passed += { .Size } + } + + // Test Read_At. + if .Read_At in mode_set { + read_buf, alloc_err := make([]u8, size) + testing.expect_value(t, alloc_err, nil, loc = loc) or_return + defer delete(read_buf) + + for start in 0.. != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read_at(stream, one_buf[:], size) + testing.expectf(t, bytes_read == 0 && err == .EOF, + "Read_At at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read_At+Size reports %v for its size after Read_At tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read_At+Size expected no error: %v", size_err, loc = loc) or_return + } + + passed += { .Read_At } + } + + // Test Read. + if .Read in mode_set { + if size > 0 { + read_buf, alloc_err := make([]u8, size) + testing.expectf(t, alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, err := io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0], + "Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + + // Test rolling back the stream one byte then reading it again. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, -1, .Current) + testing.expectf(t, pos == 0 && err == nil, + "Read+Seek Current-1 reset to 0 failed: %v, %v", pos, seek_err, loc = loc) or_return + + bytes_read, err = io.read(stream, read_buf[:1]) + testing.expectf(t, bytes_read == 1 && err == nil, + "Read 1 byte at start after Seek reset failed: %v, %v", bytes_read, err, loc = loc) or_return + testing.expectf(t, read_buf[0] == buffer[0] , + "re-Read of first byte failed: read_buf[0]<%v> != buffer[0]<%v>", read_buf[0], buffer[0], loc = loc) or_return + } + + // Make sure size is still sane. + if .Size in mode_set { + api_size, size_err := io.size(stream) + expected_api_size := size - 1 if reading_consumes else size + + testing.expectf(t, api_size == expected_api_size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, expected_api_size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + // Read the rest. + if size > 1 { + bytes_read, err = io.read(stream, read_buf[1:]) + testing.expectf(t, i64(bytes_read) == size - 1 && err == nil, + "Read rest of stream failed: %v != %v, %v", bytes_read, size-1, err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, buffer) == 0, + "Read buffer compare failed: read_buf<%v> != buffer<%v>", read_buf, buffer, loc = loc) or_return + } + } + + // Test empty streams and EOF. + one_buf: [1]u8 + bytes_read, err := io.read(stream, one_buf[:]) + testing.expectf(t, bytes_read == 0 && err == .EOF, + "Read at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return + + if !resets_on_empty && .Size in mode_set { + // Make sure size is still sane. + api_size, size_err := io.size(stream) + testing.expectf(t, api_size == size, + "Read+Size reports %v for its size after Read tests; expected %v", api_size, size, loc = loc) or_return + testing.expectf(t, size_err == nil, + "Read+Size expected no error: %v", size_err, loc = loc) or_return + } + + passed += { .Read } + } + + // Test Write_At. + if .Write_At in mode_set { + if size > 0 { + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.. != size<%v>: %v", bytes_written, size, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + bytes_read, read_err := io.read_at(stream, read_buf[:], 0) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write_At+Read_At failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write_At+Read_At buffer compare failed: write_buf<%v> != read_buf<%v>", write_buf, read_buf, loc = loc) or_return + } + } else { + // Expect that it should be okay to write a single byte to an empty stream. + x_buf: [1]u8 = { 'Z' } + + bytes_written, write_err := io.write_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_written) == 1 && write_err == nil, + "Write_At(0) with 'Z' on empty stream failed: bytes_written<%v>, %v", bytes_written, write_err, loc = loc) or_return + + // Test reading what we've written. + if .Read_At in mode_set { + x_buf[0] = 0 + bytes_read, read_err := io.read_at(stream, x_buf[:], 0) + testing.expectf(t, i64(bytes_read) == 1 && read_err == nil, + "Write_At(0)+Read_At(0) failed expectation: bytes_read<%v> != 1, %q != 'Z', %v", bytes_read, x_buf[0], read_err, loc = loc) or_return + } + } + + passed += { .Write_At } + } + + // Test Write. + if .Write in mode_set { + write_buf, write_buf_alloc_err := make([]u8, size) + testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(write_buf) + + for i in 0.., %v", pos, seek_err) or_return + } + + // Get the Size before writing. + if .Size in mode_set { + size_err: io.Error + before_write_size, size_err = io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size failed: %v", size_err, loc = loc) or_return + } + + bytes_written, write_err := io.write(stream, write_buf[:]) + testing.expectf(t, i64(bytes_written) == size && write_err == nil, + "Write %i bytes failed: %i, %v", size, bytes_written, write_err, loc = loc) or_return + + // Size sanity check, part 2. + if before_write_size >= 0 && .Size in mode_set { + after_write_size, size_err := io.size(stream) + testing.expectf(t, size_err == nil, + "Write+Size.part_2 failed: %v", size_err, loc = loc) or_return + testing.expectf(t, after_write_size == before_write_size + size, + "Write+Size.part_2 failed: %v != %v + %v", after_write_size, before_write_size, size, loc = loc) or_return + } + + // Test reading what we've written directly with Read_At. + if pos >= 0 && .Read_At in mode_set { + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read_at(stream, read_buf[:], pos) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read_At(%i) failed: bytes_read<%i> != size<%i>, %v", pos, bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Read_At buffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + // Test resetting the pointer and reading what we've written with Read. + if .Read in mode_set && .Seek in mode_set { + seek_err: io.Error + pos, seek_err = io.seek(stream, 0, .Start) + testing.expectf(t, pos == 0 && seek_err == nil, + "Write+Read+Seek(Start) failed: pos<%i>, %v", pos, seek_err) or_return + + read_buf, read_buf_alloc_err := make([]u8, size) + testing.expectf(t, read_buf_alloc_err == nil, "allocation failed", loc = loc) or_return + defer delete(read_buf) + + bytes_read, read_err := io.read(stream, read_buf[:]) + testing.expectf(t, i64(bytes_read) == size && read_err == nil, + "Write+Read failed: bytes_read<%i> != size<%i>, %v", bytes_read, size, read_err, loc = loc) or_return + testing.expectf(t, bytes.compare(read_buf, write_buf) == 0, + "Write+Readbuffer compare failed: read_buf<%v> != write_buf<%v>", read_buf, write_buf, loc = loc) or_return + } + + passed += { .Write } + } + + // Test the other modes. + if .Flush in mode_set { + err := io.flush(stream) + testing.expectf(t, err == nil, "stream failed to Flush: %v", err, loc = loc) or_return + passed += { .Flush } + } + + if .Close in mode_set { + close_err := io.close(stream) + testing.expectf(t, close_err == nil, "stream failed to Close: %v", close_err, loc = loc) or_return + passed += { .Close } + + // TODO(Feoramund): The OS<->IO interface will need updating for more + // specific error messages. + // + // This test will catch it when they start using them, but so long as + // they actually error, it should be okay for now. + // + // .EOF would be a better message for all of these. + + if .Seek in mode_set { + pos, err := io.seek(stream, 0, .Start) + + testing.expectf(t, pos == 0 && err == .Invalid_Offset, + "Seek after Close unexpected output: pos<%v>, %v", pos, err, loc = loc) or_return + } + + if .Read in mode_set { + eof_buf: [1]u8 + bytes_read, err := io.read(stream, eof_buf[:]) + testing.expectf(t, bytes_read == 0 && err == .Unknown, + "Read after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + if size > 0 && .Read_At in mode_set { + eof_buf: [1]u8 + bytes_read, err := io.read_at(stream, eof_buf[:], 0) + testing.expectf(t, bytes_read == 0 && err == .Unknown, + "Read_At after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + + if .Write in mode_set { + eof_buf: [1]u8 = {'Z'} + bytes_written, err := io.write(stream, eof_buf[:]) + testing.expectf(t, bytes_written == 0 && err == .Unknown, + "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + + if .Write_At in mode_set { + eof_buf: [1]u8 = {'Z'} + bytes_written, err := io.write_at(stream, eof_buf[:], 0) + testing.expectf(t, bytes_written == 0 && err == .Unknown, + "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + } + + if do_destroy && .Destroy in mode_set { + err := io.destroy(stream) + testing.expectf(t, err == nil, "stream failed to Destroy: %v", err, loc = loc) or_return + passed += { .Destroy } + } + + ok = true + return +} + + + +@test +test_bytes_reader :: proc(t: ^testing.T) { + buf: [32]u8 + for i in 0..\n!=\nbuffer <%q>", sb.buf[:], expected_buf[:]) + + log.debugf("%#v", results) +} + +@test +test_os_file_stream :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) + } + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + results, _ := _test_stream(t, stream, buf[:]) + + log.debugf("%#v", results) +} diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 510d66d1a..6174f2d5c 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -23,6 +23,7 @@ download_assets :: proc() { @(require) import "encoding/xml" @(require) import "flags" @(require) import "fmt" +@(require) import "io" @(require) import "math" @(require) import "math/big" @(require) import "math/linalg/glsl" From ef2cd9d97f62ec3749573f97b7a34d4a24368504 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:42:36 -0400 Subject: [PATCH 18/36] Copy missing errors in `os2._get_platform_error` from `posix` to `linux` --- core/os/os2/errors_linux.odin | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 29815bf79..ed55ea15e 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -154,6 +154,14 @@ _get_platform_error :: proc(errno: linux.Errno) -> Error { return .Exist case .ENOENT: return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory } return Platform_Error(i32(errno)) From 7683c1f4bb284e54d668b7a63a7204c7df30d5d6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:32:30 -0400 Subject: [PATCH 19/36] Report `Invalid_Whence` in `os2` POSIX seek --- core/os/os2/file_posix.odin | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 26bcb111c..dae85d224 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -419,9 +419,22 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) if n < 0 { - err = .Unknown + #partial switch posix.get_errno() { + case .EINVAL: + err = .Invalid_Offset + case: + err = .Unknown + } } return From 3ec4db212b196155683fb579a72ad71e20e622a1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:33:14 -0400 Subject: [PATCH 20/36] Report `Invalid_Whence` in `os2` Linux seek --- core/os/os2/file_linux.odin | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 64683cf1e..11be64741 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -170,11 +170,23 @@ _name :: proc(f: ^File) -> string { } _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + // We have to handle this here, because Linux returns EINVAL for both + // invalid offsets and invalid whences. + switch whence { + case .Start, .Current, .End: + break + case: + return 0, .Invalid_Whence + } n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) - if errno != .NONE { + #partial switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case .NONE: + return n, nil + case: return -1, _get_platform_error(errno) } - return n, nil } _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { From 0243647e15a7abc1f4dd567806af5f0c52823fb2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:01:30 -0400 Subject: [PATCH 21/36] Add missing `flush` functionality to `os` platforms Platforms: - FreeBSD - Haiku - Linux - NetBSD - OpenBSD --- core/os/os_freebsd.odin | 3 +-- core/os/os_haiku.odin | 6 +++++- core/os/os_linux.odin | 3 +-- core/os/os_netbsd.odin | 6 +++++- core/os/os_openbsd.odin | 6 +++++- core/sys/freebsd/syscalls.odin | 11 +++++++++++ 6 files changed, 28 insertions(+), 7 deletions(-) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 16dcf2473..affff6910 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -447,8 +447,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) } // If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 2371a909e..08bd4f832 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -142,6 +142,7 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- @@ -218,7 +219,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 81ff5077a..ba200a308 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -584,8 +584,7 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing - return nil + return _get_errno(unix.sys_fsync(int(fd))) } // If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index 130848ab0..d84be41f7 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -443,6 +443,7 @@ foreign libc { @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @@ -506,7 +507,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index b1b90b89b..d30511d9c 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -366,6 +366,7 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- @@ -430,7 +431,10 @@ close :: proc(fd: Handle) -> Error { } flush :: proc(fd: Handle) -> Error { - // do nothing + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } return nil } diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index d1876a5e2..8590df46e 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -22,6 +22,7 @@ SYS_getpid : uintptr : 20 SYS_recvfrom : uintptr : 29 SYS_accept : uintptr : 30 SYS_fcntl : uintptr : 92 +SYS_fsync : uintptr : 95 SYS_socket : uintptr : 97 SYS_connect : uintptr : 98 SYS_bind : uintptr : 104 @@ -200,6 +201,16 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { accept :: proc { accept_T, accept_nil } +// Synchronize changes to a file. +// +// The fsync() system call appeared in 4.2BSD. +fsync :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fsync, + cast(uintptr)fd) + + return cast(Errno)result +} + // File control. // // The fcntl() system call appeared in 4.2BSD. From 6aedb2695a5094aac15758ef79c3af84be27df38 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:53:04 -0400 Subject: [PATCH 22/36] Report `Invalid_Whence` on some `os` platforms - Move `Seek`-related checks into OS-specific files for granularity. Platforms: - Darwin - FreeBSD - Haiku - Linux - NetBSD - OpenBSD --- core/os/os_darwin.odin | 13 ++++++++++++- core/os/os_freebsd.odin | 14 +++++++++++++- core/os/os_haiku.odin | 13 ++++++++++++- core/os/os_linux.odin | 13 ++++++++++++- core/os/os_netbsd.odin | 13 ++++++++++++- core/os/os_openbsd.odin | 13 ++++++++++++- core/os/stream.odin | 8 -------- 7 files changed, 73 insertions(+), 14 deletions(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 1253e9ae6..2bb6c0c59 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -777,10 +777,21 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { assert(fd != -1) + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) if final_offset == -1 { - return 0, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return final_offset, nil } diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index affff6910..c05a06129 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -505,9 +505,21 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case: + return 0, errno + } } return res, nil } diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 08bd4f832..0d2c334be 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -284,9 +284,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .BAD_VALUE: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index ba200a308..9132edbfe 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -653,9 +653,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := unix.sys_lseek(int(fd), offset, whence) if res < 0 { - return -1, _get_errno(int(res)) + errno := _get_errno(int(res)) + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return i64(res), nil } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index d84be41f7..a56c0b784 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -569,9 +569,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index d30511d9c..aff78dc60 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -497,9 +497,20 @@ write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) } seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, get_last_error() + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } return res, nil } diff --git a/core/os/stream.odin b/core/os/stream.odin index c849319ae..ad8c2461c 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -47,14 +47,6 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Seek: n, os_err = seek(fd, offset, int(whence)) - if os_err != nil { - switch whence { - case .Start, .Current, .End: - return 0, .Invalid_Offset - case: - return 0, .Invalid_Whence - } - } case .Size: n, os_err = file_size(fd) case .Destroy: From 6798d15ecb96e28f35f1a38d6bbc3ea1de10b423 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:19:55 -0400 Subject: [PATCH 23/36] Check `int(abs)` instead to avoid overflows --- core/bytes/buffer.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index 0399beea4..a61f8f00d 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -323,11 +323,12 @@ buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.E return 0, .Invalid_Whence } - if abs < 0 { + abs_int := int(abs) + if abs_int < 0 { return 0, .Invalid_Offset } b.last_read = .Invalid - b.off = int(abs) + b.off = abs_int return abs, nil } From 8b40be50b92687bb4924b33f7f950721a4b9dc38 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:03:56 -0400 Subject: [PATCH 24/36] Update `core:io` tests - Relax return value requirements on errors. Only the error is checked, as there are multiple conflicting return styles throughout the `os` API. Some return the error along with `0`, `-1`, or another value. This can be smoothed out later. - Test `os2` files now. - No longer test streams after closing; this is considered similar to a use-after-free in `os2`. --- tests/core/io/test_core_io.odin | 92 ++++++++++++++++----------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 1d5df3adb..e40c79375 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -4,6 +4,7 @@ import "core:bytes" import "core:io" import "core:log" import "core:os" +import "core:os/os2" import "core:strings" import "core:testing" @@ -34,7 +35,7 @@ _test_stream :: proc( // Do some basic Seek sanity testing. if .Seek in mode_set { pos, err := io.seek(stream, 0, io.Seek_From(-1)) - testing.expectf(t, pos == 0 && err == .Invalid_Whence, + testing.expectf(t, err == .Invalid_Whence, "Seek(-1) didn't fail with Invalid_Whence: %v, %v", pos, err, loc = loc) or_return pos, err = io.seek(stream, 0, .Start) @@ -46,11 +47,11 @@ _test_stream :: proc( "Seek Current isn't 0 at the start: %v, %v", pos, err, loc = loc) or_return pos, err = io.seek(stream, -1, .Start) - testing.expectf(t, pos == 0 && err == .Invalid_Offset, + testing.expectf(t, err == .Invalid_Offset, "Seek Start-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return pos, err = io.seek(stream, -1, .Current) - testing.expectf(t, pos == 0 && err == .Invalid_Offset, + testing.expectf(t, err == .Invalid_Offset, "Seek Current-1 wasn't Invalid_Offset: %v, %v", pos, err, loc = loc) or_return pos, err = io.seek(stream, 0, .End) @@ -107,7 +108,7 @@ _test_stream :: proc( // Test empty streams and EOF. one_buf: [1]u8 bytes_read, err := io.read_at(stream, one_buf[:], size) - testing.expectf(t, bytes_read == 0 && err == .EOF, + testing.expectf(t, err == .EOF, "Read_At at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return // Make sure size is still sane. @@ -172,7 +173,7 @@ _test_stream :: proc( // Test empty streams and EOF. one_buf: [1]u8 bytes_read, err := io.read(stream, one_buf[:]) - testing.expectf(t, bytes_read == 0 && err == .EOF, + testing.expectf(t, err == .EOF, "Read at end of stream failed: %v, %v", bytes_read, err, loc = loc) or_return if !resets_on_empty && .Size in mode_set { @@ -320,49 +321,6 @@ _test_stream :: proc( close_err := io.close(stream) testing.expectf(t, close_err == nil, "stream failed to Close: %v", close_err, loc = loc) or_return passed += { .Close } - - // TODO(Feoramund): The OS<->IO interface will need updating for more - // specific error messages. - // - // This test will catch it when they start using them, but so long as - // they actually error, it should be okay for now. - // - // .EOF would be a better message for all of these. - - if .Seek in mode_set { - pos, err := io.seek(stream, 0, .Start) - - testing.expectf(t, pos == 0 && err == .Invalid_Offset, - "Seek after Close unexpected output: pos<%v>, %v", pos, err, loc = loc) or_return - } - - if .Read in mode_set { - eof_buf: [1]u8 - bytes_read, err := io.read(stream, eof_buf[:]) - testing.expectf(t, bytes_read == 0 && err == .Unknown, - "Read after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return - } - - if size > 0 && .Read_At in mode_set { - eof_buf: [1]u8 - bytes_read, err := io.read_at(stream, eof_buf[:], 0) - testing.expectf(t, bytes_read == 0 && err == .Unknown, - "Read_At after Close unexpected output: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return - } - - if .Write in mode_set { - eof_buf: [1]u8 = {'Z'} - bytes_written, err := io.write(stream, eof_buf[:]) - testing.expectf(t, bytes_written == 0 && err == .Unknown, - "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return - } - - if .Write_At in mode_set { - eof_buf: [1]u8 = {'Z'} - bytes_written, err := io.write_at(stream, eof_buf[:], 0) - testing.expectf(t, bytes_written == 0 && err == .Unknown, - "Write after Close unexpected output: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return - } } if do_destroy && .Destroy in mode_set { @@ -547,3 +505,41 @@ test_os_file_stream :: proc(t: ^testing.T) { log.debugf("%#v", results) } + +@test +test_os2_file_stream :: proc(t: ^testing.T) { + defer if !testing.failed(t) { + testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil) + } + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + // os2 file stream proc close and destroy are the same. + results, _ := _test_stream(t, stream, buf[:], do_destroy = false) + + log.debugf("%#v", results) +} From e7d6e2d163a060de7b6f2d16f5ece1ae073b7e47 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:07:53 -0400 Subject: [PATCH 25/36] Add documentation to `os2.close` Make it explicit that using a stream after closing is like a use-after-free bug. --- core/os/os2/file.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index f1c5b1fad..eedf8570c 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -132,6 +132,12 @@ name :: proc(f: ^File) -> string { return _name(f) } +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ close :: proc(f: ^File) -> Error { if f != nil { return io.close(f.stream) From de1432b31569456d59f3749d9e99132776f377dc Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:43:13 -0400 Subject: [PATCH 26/36] Fix Windows infinite recursion with `os2._flush` --- core/os/os2/file_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 284c78320..dd22ce29f 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -466,13 +466,13 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { if f != nil && f.impl != nil { - return _flush((^File_Impl)(f.impl)) + return _flush_internal((^File_Impl)(f.impl)) } return nil } _flush :: proc(f: ^File_Impl) -> Error { - return _flush(f) + return _flush_internal(f) } _flush_internal :: proc(f: ^File_Impl) -> Error { handle := _handle(&f.file) From 56f232e5fc76801d6d6872a1bbc4f67ba93c8095 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 25 Aug 2024 23:52:33 -0400 Subject: [PATCH 27/36] Report invalid whence & offset on `os2` Windows --- core/os/os2/errors_windows.odin | 3 +++ core/os/os2/file_windows.odin | 2 ++ 2 files changed, 5 insertions(+) diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6748c1167..8a9a47ca6 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -52,6 +52,9 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index dd22ce29f..2f551c49a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -248,6 +248,8 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er case .Start: w = win32.FILE_BEGIN case .Current: w = win32.FILE_CURRENT case .End: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) From 1cd5cbb0e4ac70ac773675c6dce6353679eaf6cb Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:36:00 -0400 Subject: [PATCH 28/36] Test `io` unexpected pointer movement --- tests/core/io/test_core_io.odin | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index e40c79375..3f1ecdeda 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -85,6 +85,15 @@ _test_stream :: proc( testing.expectf(t, size_err == nil, "Size expected no error: %v", size_err, loc = loc) or_return + // Ensure Size does not move the underlying pointer from the start. + // + // Some implementations may use seeking to determine file sizes. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && seek_err == nil, + "Size+Seek Current isn't 0 after getting size: %v, %v", pos, seek_err, loc = loc) or_return + } + passed += { .Size } } @@ -120,6 +129,13 @@ _test_stream :: proc( "Read_At+Size expected no error: %v", size_err, loc = loc) or_return } + // Ensure Read_At does not move the underlying pointer from the start. + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == 0 && seek_err == nil, + "Read_At+Seek Current isn't 0 after reading: %v, %v", pos, seek_err, loc = loc) or_return + } + passed += { .Read_At } } @@ -190,6 +206,15 @@ _test_stream :: proc( // Test Write_At. if .Write_At in mode_set { + // Ensure Write_At does not move the underlying pointer from the start. + starting_offset : i64 = -1 + if .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos >= 0 && seek_err == nil, + "Write_At+Seek Current failed: %v, %v", pos, seek_err, loc = loc) or_return + starting_offset = pos + } + if size > 0 { write_buf, write_buf_alloc_err := make([]u8, size) testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return @@ -231,6 +256,13 @@ _test_stream :: proc( } } + // Ensure Write_At does not move the underlying pointer from the start. + if starting_offset != -1 && .Seek in mode_set { + pos, seek_err := io.seek(stream, 0, .Current) + testing.expectf(t, pos == starting_offset && seek_err == nil, + "Write_At+Seek Current isn't %v after writing: %v, %v", starting_offset, pos, seek_err, loc = loc) or_return + } + passed += { .Write_At } } From ef99373c31bf515d9e5ab0b2749fb04771311c71 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:58:32 -0400 Subject: [PATCH 29/36] Fix `pread` and `pwrite` on `os2` Windows The previous code was jumping ahead by the specified offset, instead of getting the current offset. --- core/os/os2/file_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 2f551c49a..56ac7aaae 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -354,7 +354,7 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ @@ -423,7 +423,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) buf = buf[:MAX_RW] } - curr_offset := _seek(f, offset, .Current) or_return + curr_offset := _seek(f, 0, .Current) or_return defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ From f453054affd7b85f8c751c4f62b21788ae9d8ceb Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:45:13 -0400 Subject: [PATCH 30/36] Return `0, nil` in all `io` cases where an empty slice is provided --- core/bytes/buffer.odin | 6 ++++++ core/bytes/reader.odin | 6 ++++++ core/io/util.odin | 9 +++++++++ core/os/os2/file_linux.odin | 6 ++++++ core/os/os2/file_windows.odin | 6 +++++- core/os/stream.odin | 12 ++++++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index a61f8f00d..f4d883353 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -144,6 +144,9 @@ buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { } buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset @@ -246,6 +249,9 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io. } buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if uint(offset) >= len(b.buf) { diff --git a/core/bytes/reader.odin b/core/bytes/reader.odin index d85e4fe13..2e1c5ed42 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -34,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 { } reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if r.i >= i64(len(r.s)) { return 0, .EOF } @@ -43,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { return } reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if off < 0 { return 0, .Invalid_Offset } diff --git a/core/io/util.odin b/core/io/util.odin index 27c344890..e65a69fb3 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -340,6 +340,9 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, l := (^Limited_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if l.n <= 0 { return 0, .EOF } @@ -394,6 +397,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s := (^Section_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if s.off >= s.limit { return 0, .EOF } @@ -405,6 +411,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s.off += i64(n) return case .Read_At: + if len(p) == 0 { + return 0, nil + } p, off := p, offset if off < 0 || off >= s.limit - s.base { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 11be64741..ad6ddbf17 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -201,6 +201,9 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } @@ -226,6 +229,9 @@ _write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } if offset < 0 { return 0, .Invalid_Offset } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 56ac7aaae..47e5f0cf2 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -266,6 +266,11 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { } _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + length := len(p) + if length == 0 { + return + } + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -320,7 +325,6 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { single_read_length: win32.DWORD total_read: int - length := len(p) sync.shared_guard(&f.rw_mutex) // multiple readers diff --git a/core/os/stream.odin b/core/os/stream.odin index ad8c2461c..39edc9cd5 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -21,6 +21,9 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Flush: os_err = flush(fd) case .Read: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read(fd, p) n = i64(n_int) if n == 0 && os_err == nil { @@ -28,18 +31,27 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write(fd, p) n = i64(n_int) if n == 0 && os_err == nil { err = .EOF } case .Write_At: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == nil { From b11fa90ed249c81931d569f7a6c05060f090135e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:54:12 -0400 Subject: [PATCH 31/36] Test empty slice usage in `io` procs --- tests/core/io/test_core_io.odin | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 3f1ecdeda..b9de18c6e 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -99,6 +99,14 @@ _test_stream :: proc( // Test Read_At. if .Read_At in mode_set { + // Test reading into an empty buffer. + { + nil_slice: []u8 + bytes_read, err := io.read_at(stream, nil_slice, 0) + testing.expectf(t, bytes_read == 0 && err == nil, + "Read_At into empty slice failed: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + read_buf, alloc_err := make([]u8, size) testing.expect_value(t, alloc_err, nil, loc = loc) or_return defer delete(read_buf) @@ -141,6 +149,14 @@ _test_stream :: proc( // Test Read. if .Read in mode_set { + // Test reading into an empty buffer. + { + nil_slice: []u8 + bytes_read, err := io.read(stream, nil_slice) + testing.expectf(t, bytes_read == 0 && err == nil, + "Read into empty slice failed: bytes_read<%v>, %v", bytes_read, err, loc = loc) or_return + } + if size > 0 { read_buf, alloc_err := make([]u8, size) testing.expectf(t, alloc_err == nil, "allocation failed", loc = loc) or_return @@ -206,6 +222,14 @@ _test_stream :: proc( // Test Write_At. if .Write_At in mode_set { + // Test writing from an empty buffer. + { + nil_slice: []u8 + bytes_written, err := io.write_at(stream, nil_slice, 0) + testing.expectf(t, bytes_written == 0 && err == nil, + "Write_At from empty slice failed: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + // Ensure Write_At does not move the underlying pointer from the start. starting_offset : i64 = -1 if .Seek in mode_set { @@ -268,6 +292,14 @@ _test_stream :: proc( // Test Write. if .Write in mode_set { + // Test writing from an empty buffer. + { + nil_slice: []u8 + bytes_written, err := io.write(stream, nil_slice) + testing.expectf(t, bytes_written == 0 && err == nil, + "Write from empty slice failed: bytes_written<%v>, %v", bytes_written, err, loc = loc) or_return + } + write_buf, write_buf_alloc_err := make([]u8, size) testing.expectf(t, write_buf_alloc_err == nil, "allocation failed", loc = loc) or_return defer delete(write_buf) From 24a53c246fccb52405b84f1227be00e3bfaf2a3b Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:55:18 -0400 Subject: [PATCH 32/36] Make sure `seek` succeeds in `io.Limited_Reader` test setup --- tests/core/io/test_core_io.odin | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index b9de18c6e..56fc9a0cb 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -467,7 +467,12 @@ test_limited_reader :: proc(t: ^testing.T) { ok: bool for end in 0.., %v", pos, seek_err) { + return + } + results, ok = _test_stream(t, io.limited_reader_init(&lr, bs, end), buf[:end]) if !ok { log.debugf("buffer[:%i] := %v", end, buf[:end]) From 7fa2e56cd9e5e457919de96a22414e3a1f92a295 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 27 Aug 2024 19:17:20 -0400 Subject: [PATCH 33/36] Add `io` tests for `bufio` streams --- tests/core/io/test_core_io.odin | 120 ++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 56fc9a0cb..ad7caf22c 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -1,5 +1,6 @@ package test_core_io +import "core:bufio" import "core:bytes" import "core:io" import "core:log" @@ -612,3 +613,122 @@ test_os2_file_stream :: proc(t: ^testing.T) { log.debugf("%#v", results) } + +@test +test_bufio_buffered_writer :: proc(t: ^testing.T) { + // Using a strings.Builder as the backing stream. + + sb := strings.builder_make() + defer strings.builder_destroy(&sb) + + buf: [32]u8 + expected_buf: [64]u8 + for i in 0..\n!=\nbuffer <%q>", sb.buf[:], expected_buf[:]) + + log.debugf("%#v", results) +} + +@test +test_bufio_buffered_reader :: proc(t: ^testing.T) { + // Using a bytes.Reader as the backing stream. + + buf: [32]u8 + for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { + return + } + + flush_err := io.flush(stream) + if !testing.expectf(t, flush_err == nil, + "failed to Flush initial buffer: %v", write_err) { + return + } + + // bufio.Read_Writer isn't capable of seeking, so we have to reset the os2 + // stream back to the start here. + pos, seek_err := io.seek(stream, 0, .Start) + if !testing.expectf(t, pos == 0 && seek_err == nil, + "Pre-test Seek reset failed: pos<%v>, %v", pos, seek_err) { + return + } + + reader: bufio.Reader + writer: bufio.Writer + read_writer: bufio.Read_Writer + + bufio.reader_init(&reader, stream) + defer bufio.reader_destroy(&reader) + bufio.writer_init(&writer, stream) + defer bufio.writer_destroy(&writer) + + bufio.read_writer_init(&read_writer, &reader, &writer) + + // os2 file stream proc close and destroy are the same. + results, _ := _test_stream(t, bufio.read_writer_to_stream(&read_writer), buf[:], do_destroy = false) + + log.debugf("%#v", results) +} From 0f2ad95014a9980d41708e09995931a985948c2b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 28 Aug 2024 16:42:14 +0200 Subject: [PATCH 34/36] Fix EOF detection is os2 window read --- core/os/os2/file_windows.odin | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 47e5f0cf2..382156420 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -343,6 +343,10 @@ _read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if single_read_length > 0 && ok { total_read += int(single_read_length) + } else if single_read_length == 0 && ok { + // ok and 0 bytes means EOF: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + err = .EOF } else { err = _get_platform_error() } From fe2d256468f668ecfdee8fcc813c03cc771ece40 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 28 Aug 2024 16:50:55 +0200 Subject: [PATCH 35/36] Properly close the temporary files in io tests --- tests/core/io/test_core_io.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index ad7caf22c..0dd3d33d5 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -593,7 +593,8 @@ test_os2_file_stream :: proc(t: ^testing.T) { if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) { return } - + defer testing.expect_value(t, os2.close(fd), nil) + stream := os2.to_stream(fd) bytes_written, write_err := io.write(stream, buf[:]) @@ -693,7 +694,8 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) { if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) { return } - + defer testing.expect_value(t, os2.close(fd), nil) + stream := os2.to_stream(fd) bytes_written, write_err := io.write(stream, buf[:]) From cca385209b7b303455ad17f832add12a0afcf16d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 28 Aug 2024 16:56:56 +0200 Subject: [PATCH 36/36] Remove double close --- tests/core/io/test_core_io.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 0dd3d33d5..10c9550cb 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -593,7 +593,6 @@ test_os2_file_stream :: proc(t: ^testing.T) { if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) { return } - defer testing.expect_value(t, os2.close(fd), nil) stream := os2.to_stream(fd)