mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-07 13:33:13 +00:00
Merge pull request #4112 from Feoramund/fix-test-io-issues
Add `core:io` test suite
This commit is contained in:
@@ -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,10 +249,13 @@ 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) {
|
||||
err = .Invalid_Offset
|
||||
err = .EOF
|
||||
return
|
||||
}
|
||||
n = copy(p, b.buf[offset:])
|
||||
@@ -310,6 +316,27 @@ 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
|
||||
}
|
||||
|
||||
abs_int := int(abs)
|
||||
if abs_int < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
b.last_read = .Invalid
|
||||
b.off = abs_int
|
||||
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,14 +422,17 @@ _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))
|
||||
n = i64(buffer_length(b))
|
||||
return
|
||||
case .Destroy:
|
||||
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, .Seek, .Size, .Destroy, .Query})
|
||||
}
|
||||
return 0, .Empty
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -33,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
|
||||
}
|
||||
@@ -42,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
|
||||
}
|
||||
@@ -97,7 +104,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:
|
||||
@@ -114,6 +120,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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -376,11 +379,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
|
||||
@@ -393,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
|
||||
}
|
||||
@@ -404,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 {
|
||||
|
||||
@@ -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)
|
||||
@@ -223,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{
|
||||
@@ -247,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{
|
||||
@@ -271,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 {
|
||||
@@ -302,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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
@@ -189,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
|
||||
}
|
||||
@@ -214,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -446,7 +459,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
|
||||
|
||||
@@ -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)
|
||||
@@ -264,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
|
||||
@@ -318,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
|
||||
|
||||
@@ -337,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()
|
||||
}
|
||||
@@ -352,7 +362,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{
|
||||
@@ -421,7 +431,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{
|
||||
@@ -466,13 +476,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)
|
||||
@@ -813,7 +823,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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -446,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`.
|
||||
@@ -481,29 +481,45 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -119,49 +119,52 @@ 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="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> 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="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 ---
|
||||
@(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
|
||||
@@ -216,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
|
||||
}
|
||||
|
||||
@@ -250,29 +256,48 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
@@ -654,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
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
@@ -441,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 ---
|
||||
@@ -504,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
|
||||
}
|
||||
|
||||
@@ -535,29 +541,48 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -343,50 +343,53 @@ 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="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> 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="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 ---
|
||||
@(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)
|
||||
@@ -428,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
|
||||
}
|
||||
|
||||
@@ -463,29 +469,48 @@ 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
@@ -58,5 +70,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
|
||||
}
|
||||
|
||||
@@ -14,12 +14,15 @@ 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
|
||||
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
|
||||
@@ -29,12 +32,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.
|
||||
@@ -164,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.
|
||||
@@ -469,6 +516,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
735
tests/core/io/test_core_io.odin
Normal file
735
tests/core/io/test_core_io.odin
Normal file
@@ -0,0 +1,735 @@
|
||||
package test_core_io
|
||||
|
||||
import "core:bufio"
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
import "core:os/os2"
|
||||
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, 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, 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, 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
|
||||
|
||||
// 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 }
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
for start in 0..<size {
|
||||
for end in 1+start..<size {
|
||||
subsize := end - start
|
||||
bytes_read, err := io.read_at(stream, read_buf[:subsize], start)
|
||||
testing.expectf(t, i64(bytes_read) == subsize && err == nil,
|
||||
"Read_At(%i) of %v bytes failed: %v, %v", start, subsize, bytes_read, err, loc = loc) or_return
|
||||
testing.expectf(t, bytes.compare(read_buf[:subsize], buffer[start:end]) == 0,
|
||||
"Read_At 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_at(stream, one_buf[:], size)
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
// 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 }
|
||||
}
|
||||
|
||||
// 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
|
||||
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, 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 {
|
||||
// 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 {
|
||||
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
|
||||
defer delete(write_buf)
|
||||
|
||||
for i in 0..<size {
|
||||
write_buf[i] = buffer[i] ~ 0xAA
|
||||
}
|
||||
|
||||
bytes_written, write_err := io.write_at(stream, write_buf[:], 0)
|
||||
testing.expectf(t, i64(bytes_written) == size && write_err == nil,
|
||||
"Write_At failed: bytes_written<%v> != 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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 }
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
for i in 0..<size {
|
||||
write_buf[i] = buffer[i] ~ 0xAA
|
||||
}
|
||||
|
||||
pos: i64 = -1
|
||||
before_write_size: i64 = -1
|
||||
|
||||
// Do a Seek sanity check after past tests.
|
||||
if .Seek in mode_set {
|
||||
seek_err: io.Error
|
||||
pos, seek_err = io.seek(stream, 0, .Current)
|
||||
testing.expectf(t, seek_err == nil,
|
||||
"Write+Seek(Current) failed: pos<%i>, %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 }
|
||||
}
|
||||
|
||||
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..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
br: bytes.Reader
|
||||
|
||||
results: Passed_Tests
|
||||
ok: bool
|
||||
|
||||
for end in 0..<i64(len(buf)) {
|
||||
results, ok =_test_stream(t, bytes.reader_init(&br, buf[:end]), buf[:end])
|
||||
if !ok {
|
||||
log.debugf("buffer[:%i] := %v", end, buf[:end])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.debugf("%#v", results)
|
||||
}
|
||||
|
||||
@test
|
||||
test_bytes_buffer_stream :: proc(t: ^testing.T) {
|
||||
buf: [32]u8
|
||||
for i in 0..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
results: Passed_Tests
|
||||
ok: bool
|
||||
|
||||
for end in 0..<i64(len(buf)) {
|
||||
bb: bytes.Buffer
|
||||
// Mind that `bytes.buffer_init` copies the entire underlying slice.
|
||||
bytes.buffer_init(&bb, buf[:end])
|
||||
|
||||
// `bytes.Buffer` has a behavior of decreasing its size with each read
|
||||
// until it eventually clears the underlying buffer when it runs out of
|
||||
// data to read.
|
||||
results, ok = _test_stream(t, bytes.buffer_to_stream(&bb), buf[:end],
|
||||
reading_consumes = true, resets_on_empty = true)
|
||||
if !ok {
|
||||
log.debugf("buffer[:%i] := %v", end, buf[:end])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.debugf("%#v", results)
|
||||
}
|
||||
|
||||
@test
|
||||
test_limited_reader :: proc(t: ^testing.T) {
|
||||
buf: [32]u8
|
||||
for i in 0..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
br: bytes.Reader
|
||||
bs := bytes.reader_init(&br, buf[:])
|
||||
|
||||
lr: io.Limited_Reader
|
||||
|
||||
results: Passed_Tests
|
||||
ok: bool
|
||||
|
||||
for end in 0..<i64(len(buf)) {
|
||||
pos, seek_err := io.seek(bs, 0, .Start)
|
||||
if !testing.expectf(t, pos == 0 && seek_err == nil,
|
||||
"Pre-test Seek reset failed: pos<%v>, %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])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.debugf("%#v", results)
|
||||
}
|
||||
|
||||
@test
|
||||
test_section_reader :: proc(t: ^testing.T) {
|
||||
buf: [32]u8
|
||||
for i in 0..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
br: bytes.Reader
|
||||
bs := bytes.reader_init(&br, buf[:])
|
||||
|
||||
sr: io.Section_Reader
|
||||
|
||||
results: Passed_Tests
|
||||
ok: bool
|
||||
|
||||
for start in 0..<i64(len(buf)) {
|
||||
for end in start..<i64(len(buf)) {
|
||||
results, ok = _test_stream(t, io.section_reader_init(&sr, bs, start, end-start), buf[start:end])
|
||||
if !ok {
|
||||
log.debugf("buffer[%i:%i] := %v", start, end, buf[start:end])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debugf("%#v", results)
|
||||
}
|
||||
|
||||
@test
|
||||
test_string_builder_stream :: proc(t: ^testing.T) {
|
||||
sb := strings.builder_make()
|
||||
defer strings.builder_destroy(&sb)
|
||||
|
||||
// String builders do not support reading, so we'll have to set up a few
|
||||
// things outside the main test.
|
||||
|
||||
buf: [32]u8
|
||||
expected_buf: [64]u8
|
||||
for i in 0..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
expected_buf[i] = 'A' + i
|
||||
strings.write_byte(&sb, 'A' + i)
|
||||
}
|
||||
for i in 32..<u8(len(expected_buf)) {
|
||||
expected_buf[i] = ('A' + i-len(buf)) ~ 0xAA
|
||||
}
|
||||
|
||||
results, _ := _test_stream(t, strings.to_stream(&sb), buf[:],
|
||||
do_destroy = false)
|
||||
|
||||
testing.expectf(t, bytes.compare(sb.buf[:], expected_buf[:]) == 0, "string builder stream failed:\nbuilder<%q>\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..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
TEMPORARY_FILENAME :: "test_core_io_os_file_stream"
|
||||
|
||||
fd, open_err := os.open(TEMPORARY_FILENAME, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0o644)
|
||||
if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) {
|
||||
return
|
||||
}
|
||||
|
||||
stream := os.stream_from_handle(fd)
|
||||
|
||||
bytes_written, write_err := io.write(stream, buf[:])
|
||||
if !testing.expectf(t, bytes_written == len(buf) && write_err == nil,
|
||||
"failed to Write initial buffer: bytes_written<%v> != 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)
|
||||
}
|
||||
|
||||
@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..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
TEMPORARY_FILENAME :: "test_core_io_os2_file_stream"
|
||||
|
||||
fd, open_err := os2.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc})
|
||||
if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) {
|
||||
return
|
||||
}
|
||||
|
||||
stream := os2.to_stream(fd)
|
||||
|
||||
bytes_written, write_err := io.write(stream, buf[:])
|
||||
if !testing.expectf(t, bytes_written == len(buf) && write_err == nil,
|
||||
"failed to Write initial buffer: bytes_written<%v> != 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)
|
||||
}
|
||||
|
||||
@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..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
expected_buf[i] = 'A' + i
|
||||
strings.write_byte(&sb, 'A' + i)
|
||||
}
|
||||
for i in 32..<u8(len(expected_buf)) {
|
||||
expected_buf[i] = ('A' + i-len(buf)) ~ 0xAA
|
||||
}
|
||||
|
||||
writer: bufio.Writer
|
||||
bufio.writer_init(&writer, strings.to_stream(&sb))
|
||||
defer bufio.writer_destroy(&writer)
|
||||
|
||||
results, _ := _test_stream(t, bufio.writer_to_stream(&writer), buf[:],
|
||||
do_destroy = false)
|
||||
|
||||
testing.expectf(t, bytes.compare(sb.buf[:], expected_buf[:]) == 0, "bufio buffered string builder stream failed:\nbuilder<%q>\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..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
results: Passed_Tests
|
||||
ok: bool
|
||||
|
||||
for end in 0..<i64(len(buf)) {
|
||||
br: bytes.Reader
|
||||
bs := bytes.reader_init(&br, buf[:end])
|
||||
|
||||
reader: bufio.Reader
|
||||
bufio.reader_init(&reader, bs)
|
||||
defer bufio.reader_destroy(&reader)
|
||||
|
||||
results, ok = _test_stream(t, bufio.reader_to_stream(&reader), buf[:end])
|
||||
if !ok {
|
||||
log.debugf("buffer[:%i] := %v", end, buf[:end])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.debugf("%#v", results)
|
||||
}
|
||||
|
||||
@test
|
||||
test_bufio_buffered_read_writer :: proc(t: ^testing.T) {
|
||||
// Using an os2.File as the backing stream for both reader & writer.
|
||||
|
||||
defer if !testing.failed(t) {
|
||||
testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil)
|
||||
}
|
||||
|
||||
buf: [32]u8
|
||||
for i in 0..<u8(len(buf)) {
|
||||
buf[i] = 'A' + i
|
||||
}
|
||||
|
||||
TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os2_file_stream"
|
||||
|
||||
fd, open_err := os2.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc})
|
||||
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[:])
|
||||
if !testing.expectf(t, bytes_written == len(buf) && write_err == nil,
|
||||
"failed to Write initial buffer: bytes_written<%v> != 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)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user