From: Brendan Hansen Date: Fri, 3 Dec 2021 17:42:34 +0000 (-0600) Subject: moved file library to os package X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ab3b86ecb510ffb66d7cbb6fd29b95577ee2e8ac;p=onyx.git moved file library to os package --- diff --git a/core/io/file.onyx b/core/io/file.onyx deleted file mode 100644 index 81eaad09..00000000 --- a/core/io/file.onyx +++ /dev/null @@ -1,352 +0,0 @@ -@TODO // Move this file to the core.os package -package core.io - -#local runtime :: package runtime -#if runtime.Runtime != runtime.Runtime_Wasi - && runtime.Runtime != runtime.Runtime_Onyx { - #error "The file system library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." -} - -use package core - -#local wasi :: package wasi -use package wasi { - FileDescriptor, - FDFlags, OFlags, Rights, - LookupFlags, Errno, - IOVec, IOVecArray, Size, - FileStat, Whence -} - -OpenMode :: enum { - Invalid; - Read; - Write; - Append; -} - -File :: struct { - fd : FileDescriptor; - - mode : OpenMode = .Invalid; - rights : Rights = ~~ 0; - flags : FDFlags = ~~ 0; -} - -file_open :: (path: str, mode := OpenMode.Read, flags := FDFlags.Sync) -> (File, bool) { - // Requesting all of the rights because why not. - rights := - Rights.DataSync - | Rights.Read - | Rights.Seek - | Rights.FdStatSetFlags - | Rights.Sync - | Rights.Tell - | Rights.Write - | Rights.Advise - | Rights.Allocate - | Rights.PathCreateDirectory - | Rights.PathCreateFile - | Rights.PathLinkSource - | Rights.PathLinkTarget - | Rights.PathOpen - | Rights.ReadDir - | Rights.PathReadlink - | Rights.PathRenameSource - | Rights.PathRenameTarget - | Rights.PathFilestatGet - | Rights.PathFilestateSetSize - | Rights.PathFilestateSetTimes - | Rights.FilestatGet - | Rights.FilestatSetSize - | Rights.FilestatSetTimes - | Rights.PathSymlink - | Rights.PathRemoveDirectory - | Rights.PathUnlinkFile - | Rights.PollFDReadWrite; - - open_flags := cast(OFlags) 0; - fd_flags := flags; - - switch mode { - case .Write { - open_flags |= OFlags.Creat | OFlags.Trunc; - rights |= Rights.Write | Rights.Seek | Rights.Tell; - } - - case .Append { - open_flags |= OFlags.Creat; - rights |= Rights.Write | Rights.Seek | Rights.Tell; - fd_flags |= FDFlags.Append; - } - - case .Read { - rights |= Rights.Read | Rights.Seek | Rights.Tell; - } - } - - file := File.{ fd = -1 }; - file.mode = mode; - file.rights = rights; - file.flags = fd_flags; - - // Currently the directory's file descriptor appears to always be 3 - // However, this is not necessarily correct, so also try a preopened directory - for DIR_FD: .[ 3, 4 ] { - if err := wasi.path_open( - DIR_FD, - .SymLinkFollow, - path, - open_flags, - rights, - rights, - fd_flags, - ^file.fd); - err == .Success { - return file, true; - } - } - - return file, false; -} - -file_close :: (file: File) -> bool { - if wasi.fd_close(file.fd) != .Success { - return false; - } - - return true; -} - -file_write :: (file: File, data: str) { - vec := IOVec.{ buf = cast(u32) data.data, len = data.count }; - tmp : Size; - wasi.fd_write(file.fd, .{ cast(u32) ^vec, 1 }, ^tmp); - wasi.fd_datasync(file.fd); -} - -file_get_size :: (file: File) -> u64 { - fs: FileStat; - if wasi.fd_filestat_get(file.fd, ^fs) != Errno.Success do return 0; - - return fs.size; -} - -get_contents_from_file :: (file: File) -> str { - size := cast(u32) file_get_size(file); - - data := cast(^u8) raw_alloc(context.allocator, size); - - prev_loc: i64; - wasi.fd_tell(file.fd, ^prev_loc); - - dummy: i64; - wasi.fd_seek(file.fd, 0, .Set, ^dummy); - - dummy2: u32; - buf := IOVec.{ cast(u32) data, size }; - wasi.fd_pread(file.fd, .{ cast(u32) ^buf, 1 }, 0, ^dummy2); - - wasi.fd_seek(file.fd, prev_loc, .Set, ^dummy); - - return data[0 .. size]; -} - -get_contents :: #match { - get_contents_from_file, - - (path: str) -> str { - tmp_file, success := file_open(path, .Read); - if !success do return .{ null, 0 }; - defer file_close(tmp_file); - - return get_contents(tmp_file); - } -} - -file_exists :: (path: str) -> bool { - fs: wasi.FileStat; - - exists := false; - for .[3, 4] { // Trying both preopened directories - err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); - if err == .Success do exists = true; - } - - return exists; -} - -FileStream :: struct { - use stream : Stream; - use file : File; -} - -open :: (path: str, mode := OpenMode.Read) -> (Error, FileStream) { - fs := FileStream.{ - stream = .{ vtable = null }, - file = .{ fd = -1 }, - }; - - file, success := file_open(path, mode); - if !success do return .NotFound, fs; - - fs.file = file; - fs.vtable = ^wasi_file_stream_vtable; - return .None, fs; -} - -close :: (file: ^FileStream) { - file_close(file.file); - file.stream.vtable = null; -} - -with_file :: (path: str, mode := OpenMode.Read) -> Iterator(^FileStream) { - Context :: struct { - valid_file_stream := false; - - file_stream: FileStream; - } - - c := new(Context); - if err, file_stream := open(path, mode); err == .None { - c.file_stream = file_stream; - c.valid_file_stream = true; - } - - next :: (use c: ^Context) -> (^FileStream, bool) { - if !valid_file_stream do return null, false; - - defer valid_file_stream = false; - return ^file_stream, true; - } - - close_context :: (use c: ^Context) { - close(^file_stream); - cfree(c); - } - - return .{ c, next, close_context }; -} - -#package -wasi_file_stream_vtable := Stream_Vtable.{ - seek = (use fs: ^FileStream, to: i32, whence: SeekFrom) -> Error { - // Currently, the new offset is just ignored. - newoffset : wasi.Filesize; - error := wasi.fd_seek(file.fd, ~~ to, ~~ whence, ^newoffset); - if error != .Success do return .BadFile; - - return Error.None; - }, - - tell = (use fs: ^FileStream) -> (Error, u32) { - location : wasi.Filesize; - error := wasi.fd_tell(file.fd, ^location); - if error != .Success do return .BadFile, 0; - - return .None, ~~location; - }, - - read = (use fs: ^FileStream, buffer: [] u8) -> (Error, u32) { - bytes_read : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_read(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_read); - if error != .Success do return .BadFile, 0; - - return .None, bytes_read; - }, - - read_at = (use fs: ^FileStream, at: u32, buffer: [] u8) -> (Error, u32) { - bytes_read : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_pread(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ~~at, ^bytes_read); - - // FIX: Maybe report Error.OutOfBounds if the 'at' was out of bounds? - if error != .Success do return .BadFile, 0; - - return .None, bytes_read; - }, - - read_byte = (use fs: ^FileStream) -> (Error, u8) { - bytes_read : wasi.Size; - byte : u8; - vec := IOVec.{ buf = cast(u32) ^byte, len = 1}; - error := wasi.fd_read(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_read); - if error != .Success do return .BadFile, 0; - - return .None, byte; - }, - - write = (use fs: ^FileStream, buffer: [] u8) -> (Error, u32) { - bytes_written : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_write(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_written); - if error != .Success do return .BadFile, 0; - - return .None, bytes_written; - }, - - write_at = (use fs: ^FileStream, at: u32, buffer: [] u8) -> (Error, u32) { - bytes_written : wasi.Size; - vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; - error := wasi.fd_pwrite(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ~~at, ^bytes_written); - - // FIX: Maybe report Error.OutOfBounds if the 'at' was out of bounds? - if error != .Success do return .BadFile, 0; - - return .None, bytes_written; - }, - - write_byte = (use fs: ^FileStream, byte: u8) -> Error { - bytes_written : wasi.Size; - byte_to_write := byte; - vec := IOVec.{ buf = cast(u32) ^byte_to_write, len = 1 }; - error := wasi.fd_write(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_written); - if error != .Success do return .BadFile; - - return .None; - }, - - close = (use fs: ^FileStream) -> Error { - file_close(file); - return .None; - }, - - flush = (use fs: ^FileStream) -> Error { - wasi.fd_datasync(file.fd); - return .None; - }, - - size = (use fs: ^FileStream) -> i32 { - file_stat: FileStat; - if wasi.fd_filestat_get(file.fd, ^file_stat) != .Success do return 0; - - return ~~ file_stat.size; - }, -} - - - - -file_logger_open :: (filename: str, allocator := context.allocator) -> Logger { - file := new(File, allocator); - success := false; - - *file, success = file_open(filename, mode=.Append); - assert(success, "Unable to open file for logging."); - - return .{ file_logger_proc, file }; -} - -file_logger_close :: (logger := context.logger) { - file_close(*cast(^File) logger.data); - - // @Robustness: this could be the wrong allocator if the context allocator wasn't used. - cfree(logger.data); -} - -#local -file_logger_proc :: (data: ^File, msg: str) { - file_write(*data, msg); - file_write(*data, "\n"); -} diff --git a/core/os/file.onyx b/core/os/file.onyx new file mode 100644 index 00000000..5518e2a5 --- /dev/null +++ b/core/os/file.onyx @@ -0,0 +1,351 @@ +package core.os + +#local runtime :: package runtime +#if runtime.Runtime != runtime.Runtime_Wasi + && runtime.Runtime != runtime.Runtime_Onyx { + #error "The file system library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." +} + +use package core + +#local wasi :: package wasi +use package wasi { + FileDescriptor, + FDFlags, OFlags, Rights, + LookupFlags, Errno, + IOVec, IOVecArray, Size, + FileStat, Whence +} + +OpenMode :: enum { + Invalid; + Read; + Write; + Append; +} + +File :: struct { + fd : FileDescriptor; + + mode : OpenMode = .Invalid; + rights : Rights = ~~ 0; + flags : FDFlags = ~~ 0; +} + +file_open :: (path: str, mode := OpenMode.Read, flags := FDFlags.Sync) -> (File, bool) { + // Requesting all of the rights because why not. + rights := + Rights.DataSync + | Rights.Read + | Rights.Seek + | Rights.FdStatSetFlags + | Rights.Sync + | Rights.Tell + | Rights.Write + | Rights.Advise + | Rights.Allocate + | Rights.PathCreateDirectory + | Rights.PathCreateFile + | Rights.PathLinkSource + | Rights.PathLinkTarget + | Rights.PathOpen + | Rights.ReadDir + | Rights.PathReadlink + | Rights.PathRenameSource + | Rights.PathRenameTarget + | Rights.PathFilestatGet + | Rights.PathFilestateSetSize + | Rights.PathFilestateSetTimes + | Rights.FilestatGet + | Rights.FilestatSetSize + | Rights.FilestatSetTimes + | Rights.PathSymlink + | Rights.PathRemoveDirectory + | Rights.PathUnlinkFile + | Rights.PollFDReadWrite; + + open_flags := cast(OFlags) 0; + fd_flags := flags; + + switch mode { + case .Write { + open_flags |= OFlags.Creat | OFlags.Trunc; + rights |= Rights.Write | Rights.Seek | Rights.Tell; + } + + case .Append { + open_flags |= OFlags.Creat; + rights |= Rights.Write | Rights.Seek | Rights.Tell; + fd_flags |= FDFlags.Append; + } + + case .Read { + rights |= Rights.Read | Rights.Seek | Rights.Tell; + } + } + + file := File.{ fd = -1 }; + file.mode = mode; + file.rights = rights; + file.flags = fd_flags; + + // Currently the directory's file descriptor appears to always be 3 + // However, this is not necessarily correct, so also try a preopened directory + for DIR_FD: .[ 3, 4 ] { + if err := wasi.path_open( + DIR_FD, + .SymLinkFollow, + path, + open_flags, + rights, + rights, + fd_flags, + ^file.fd); + err == .Success { + return file, true; + } + } + + return file, false; +} + +file_close :: (file: File) -> bool { + if wasi.fd_close(file.fd) != .Success { + return false; + } + + return true; +} + +file_write :: (file: File, data: str) { + vec := IOVec.{ buf = cast(u32) data.data, len = data.count }; + tmp : Size; + wasi.fd_write(file.fd, .{ cast(u32) ^vec, 1 }, ^tmp); + wasi.fd_datasync(file.fd); +} + +file_get_size :: (file: File) -> u64 { + fs: FileStat; + if wasi.fd_filestat_get(file.fd, ^fs) != Errno.Success do return 0; + + return fs.size; +} + +get_contents_from_file :: (file: File) -> str { + size := cast(u32) file_get_size(file); + + data := cast(^u8) raw_alloc(context.allocator, size); + + prev_loc: i64; + wasi.fd_tell(file.fd, ^prev_loc); + + dummy: i64; + wasi.fd_seek(file.fd, 0, .Set, ^dummy); + + dummy2: u32; + buf := IOVec.{ cast(u32) data, size }; + wasi.fd_pread(file.fd, .{ cast(u32) ^buf, 1 }, 0, ^dummy2); + + wasi.fd_seek(file.fd, prev_loc, .Set, ^dummy); + + return data[0 .. size]; +} + +get_contents :: #match { + get_contents_from_file, + + (path: str) -> str { + tmp_file, success := file_open(path, .Read); + if !success do return .{ null, 0 }; + defer file_close(tmp_file); + + return get_contents(tmp_file); + } +} + +file_exists :: (path: str) -> bool { + fs: wasi.FileStat; + + exists := false; + for .[3, 4] { // Trying both preopened directories + err := wasi.path_filestat_get(it, .SymLinkFollow, path, ^fs); + if err == .Success do exists = true; + } + + return exists; +} + +FileStream :: struct { + use stream : io.Stream; + use file : File; +} + +open :: (path: str, mode := OpenMode.Read) -> (io.Error, FileStream) { + fs := FileStream.{ + stream = .{ vtable = null }, + file = .{ fd = -1 }, + }; + + file, success := file_open(path, mode); + if !success do return .NotFound, fs; + + fs.file = file; + fs.vtable = ^wasi_file_stream_vtable; + return .None, fs; +} + +close :: (file: ^FileStream) { + file_close(file.file); + file.stream.vtable = null; +} + +with_file :: (path: str, mode := OpenMode.Read) -> Iterator(^FileStream) { + Context :: struct { + valid_file_stream := false; + + file_stream: FileStream; + } + + c := new(Context); + if err, file_stream := open(path, mode); err == .None { + c.file_stream = file_stream; + c.valid_file_stream = true; + } + + next :: (use c: ^Context) -> (^FileStream, bool) { + if !valid_file_stream do return null, false; + + defer valid_file_stream = false; + return ^file_stream, true; + } + + close_context :: (use c: ^Context) { + close(^file_stream); + cfree(c); + } + + return .{ c, next, close_context }; +} + +#package +wasi_file_stream_vtable := io.Stream_Vtable.{ + seek = (use fs: ^FileStream, to: i32, whence: io.SeekFrom) -> io.Error { + // Currently, the new offset is just ignored. + newoffset : wasi.Filesize; + error := wasi.fd_seek(file.fd, ~~ to, ~~ whence, ^newoffset); + if error != .Success do return .BadFile; + + return .None; + }, + + tell = (use fs: ^FileStream) -> (io.Error, u32) { + location : wasi.Filesize; + error := wasi.fd_tell(file.fd, ^location); + if error != .Success do return .BadFile, 0; + + return .None, ~~location; + }, + + read = (use fs: ^FileStream, buffer: [] u8) -> (io.Error, u32) { + bytes_read : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_read(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_read); + if error != .Success do return .BadFile, 0; + + return .None, bytes_read; + }, + + read_at = (use fs: ^FileStream, at: u32, buffer: [] u8) -> (io.Error, u32) { + bytes_read : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_pread(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ~~at, ^bytes_read); + + // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? + if error != .Success do return .BadFile, 0; + + return .None, bytes_read; + }, + + read_byte = (use fs: ^FileStream) -> (io.Error, u8) { + bytes_read : wasi.Size; + byte : u8; + vec := IOVec.{ buf = cast(u32) ^byte, len = 1}; + error := wasi.fd_read(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_read); + if error != .Success do return .BadFile, 0; + + return .None, byte; + }, + + write = (use fs: ^FileStream, buffer: [] u8) -> (io.Error, u32) { + bytes_written : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_write(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_written); + if error != .Success do return .BadFile, 0; + + return .None, bytes_written; + }, + + write_at = (use fs: ^FileStream, at: u32, buffer: [] u8) -> (io.Error, u32) { + bytes_written : wasi.Size; + vec := IOVec.{ buf = cast(u32) buffer.data, len = buffer.count }; + error := wasi.fd_pwrite(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ~~at, ^bytes_written); + + // FIX: Maybe report io.Error.OutOfBounds if the 'at' was out of bounds? + if error != .Success do return .BadFile, 0; + + return .None, bytes_written; + }, + + write_byte = (use fs: ^FileStream, byte: u8) -> io.Error { + bytes_written : wasi.Size; + byte_to_write := byte; + vec := IOVec.{ buf = cast(u32) ^byte_to_write, len = 1 }; + error := wasi.fd_write(file.fd, IOVecArray.{ cast(u32) ^vec, 1 }, ^bytes_written); + if error != .Success do return .BadFile; + + return .None; + }, + + close = (use fs: ^FileStream) -> io.Error { + file_close(file); + return .None; + }, + + flush = (use fs: ^FileStream) -> io.Error { + wasi.fd_datasync(file.fd); + return .None; + }, + + size = (use fs: ^FileStream) -> i32 { + file_stat: FileStat; + if wasi.fd_filestat_get(file.fd, ^file_stat) != .Success do return 0; + + return ~~ file_stat.size; + }, +} + + + + +file_logger_open :: (filename: str, allocator := context.allocator) -> Logger { + file := new(File, allocator); + success := false; + + *file, success = file_open(filename, mode=.Append); + assert(success, "Unable to open file for logging."); + + return .{ file_logger_proc, file }; +} + +file_logger_close :: (logger := context.logger) { + file_close(*cast(^File) logger.data); + + // @Robustness: this could be the wrong allocator if the context allocator wasn't used. + cfree(logger.data); +} + +#local +file_logger_proc :: (data: ^File, msg: str) { + file_write(*data, msg); + file_write(*data, "\n"); +} diff --git a/core/std.onyx b/core/std.onyx index ea66bc54..c076b04f 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -41,7 +41,7 @@ package core #load "./wasi/wasi" #load "./wasi/env" #load "./wasi/clock" - #load "./io/file" + #load "./os/file" #load "./os/os" } diff --git a/scripts/run_tests.onyx b/scripts/run_tests.onyx index cb9affab..40bdffbd 100644 --- a/scripts/run_tests.onyx +++ b/scripts/run_tests.onyx @@ -101,7 +101,7 @@ find_onyx_files :: (root: str, cases: ^[..] Test_Case) { test_case := string.concat(path_buffer, root, "/", it.name) |> string.alloc_copy(); expected_file := test_case[0 .. (test_case.count - 5)]; - if !io.file_exists(expected_file) { + if !os.file_exists(expected_file) { print_color(.Yellow, "Skipping test case {} because an expected output file was not found.\n", test_case); continue; } @@ -137,7 +137,7 @@ test_cases :: (cases) => { continue; } - for expected_file: io.with_file(it.expected_file) { + for expected_file: os.with_file(it.expected_file) { expected_reader := io.reader_make(expected_file); expected_output := io.read_all(^expected_reader); diff --git a/tests/aoc-2021/day01.onyx b/tests/aoc-2021/day01.onyx index d1de70f9..8664c136 100644 --- a/tests/aoc-2021/day01.onyx +++ b/tests/aoc-2021/day01.onyx @@ -14,16 +14,10 @@ count_increasing :: (arr: [] $T) -> u32 { } main :: (args) => { - #if false { - contents := #file_contents "./input/day01.txt"; - reader, stream := io.reader_from_string(contents); - defer cfree(stream); - } - - err, file := io.open("./tests/aoc-2021/input/day01.txt"); + err, file := os.open("./tests/aoc-2021/input/day01.txt"); assert(err == .None, "Error opening file"); reader := io.reader_make(^file); - defer io.close(^file); + defer os.close(^file); nums := array.make(i32); while !io.reader_empty(^reader) { diff --git a/tests/aoc-2021/day02.onyx b/tests/aoc-2021/day02.onyx index 1f19319b..ca1704c1 100644 --- a/tests/aoc-2021/day02.onyx +++ b/tests/aoc-2021/day02.onyx @@ -5,7 +5,7 @@ PART :: 2 use package core main :: (args) => { - for file: io.with_file("tests/aoc-2021/input/day02.txt") { + for file: os.with_file("tests/aoc-2021/input/day02.txt") { reader := io.reader_make(file); #if PART == 1 { diff --git a/tests/aoc-2021/day03.onyx b/tests/aoc-2021/day03.onyx index ad8d4689..fe2b70ba 100644 --- a/tests/aoc-2021/day03.onyx +++ b/tests/aoc-2021/day03.onyx @@ -26,7 +26,7 @@ read_binary :: (r: ^io.Reader) -> i32 { main :: (args) => { BITS :: 12 - for io.with_file("./tests/aoc-2021/input/day03.txt") { + for os.with_file("./tests/aoc-2021/input/day03.txt") { reader := io.reader_make(it); nums := array.make(i32);