From ec314c8324469e188d4568029cba8950377fabc5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:42:17 +0100 Subject: [PATCH 01/59] fix conflict --- core/crypto/hash/hash_os.odin | 41 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin index d54e657ad..32347c452 100644 --- a/core/crypto/hash/hash_os.odin +++ b/core/crypto/hash/hash_os.odin @@ -2,25 +2,25 @@ package crypto_hash import "core:io" -import "core:os" +import os "core:os/os2" -// hash_file will read the file provided by the given handle and return the +// `hash_file` will read the file provided by the given handle and return the // computed digest in a newly allocated slice. -hash_file :: proc( - algorithm: Algorithm, - hd: os.Handle, +hash_file_by_handle :: proc( + algorithm: Algorithm, + handle: ^os.File, load_at_once := false, - allocator := context.allocator, + allocator := context.allocator, ) -> ( []byte, io.Error, ) { if !load_at_once { - return hash_stream(algorithm, os.stream_from_handle(hd), allocator) + return hash_stream(algorithm, handle.stream, allocator) } - buf, ok := os.read_entire_file(hd, allocator) - if !ok { + buf, err := os.read_entire_file(handle, allocator) + if err != nil { return nil, io.Error.Unknown } defer delete(buf, allocator) @@ -28,11 +28,30 @@ hash_file :: proc( return hash_bytes(algorithm, buf, allocator), io.Error.None } +hash_file_by_name :: proc( + algorithm: Algorithm, + filename: string, + load_at_once := false, + allocator := context.allocator, +) -> ( + []byte, + io.Error, +) { + handle, err := os.open(filename) + defer os.close(handle) + + if err != nil { + return {}, io.Error.Unknown + } + return hash_file_by_handle(algorithm, handle, load_at_once, allocator) +} + + hash :: proc { hash_stream, - hash_file, + hash_file_by_handle, hash_bytes, hash_string, hash_bytes_to_buffer, hash_string_to_buffer, -} +} \ No newline at end of file From 3303d3c98f54164ef9a2130c12f12ea6980cea88 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Oct 2025 23:08:56 +0100 Subject: [PATCH 02/59] Stub out `core:os/os2` for js_wasm --- core/os/os2/dir_js.odin | 24 ++++++++ core/os/os2/env_js.odin | 42 +++++++++++++ core/os/os2/errors_js.odin | 13 +++++ core/os/os2/file_js.odin | 107 ++++++++++++++++++++++++++++++++++ core/os/os2/heap_js.odin | 7 +++ core/os/os2/path_js.odin | 85 +++++++++++++++++++++++++++ core/os/os2/pipe_js.odin | 14 +++++ core/os/os2/process_js.odin | 87 +++++++++++++++++++++++++++ core/os/os2/stat_js.odin | 21 +++++++ core/os/os2/temp_file_js.odin | 9 +++ 10 files changed, 409 insertions(+) create mode 100644 core/os/os2/dir_js.odin create mode 100644 core/os/os2/env_js.odin create mode 100644 core/os/os2/errors_js.odin create mode 100644 core/os/os2/file_js.odin create mode 100644 core/os/os2/heap_js.odin create mode 100644 core/os/os2/path_js.odin create mode 100644 core/os/os2/pipe_js.odin create mode 100644 core/os/os2/process_js.odin create mode 100644 core/os/os2/stat_js.odin create mode 100644 core/os/os2/temp_file_js.odin diff --git a/core/os/os2/dir_js.odin b/core/os/os2/dir_js.odin new file mode 100644 index 000000000..d8f7c6202 --- /dev/null +++ b/core/os/os2/dir_js.odin @@ -0,0 +1,24 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:intrinsics" + +Read_Directory_Iterator_Impl :: struct { + fullpath: [dynamic]byte, + buf: []byte, + off: int, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return {}, -1, false +} + +_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + +} diff --git a/core/os/os2/env_js.odin b/core/os/os2/env_js.odin new file mode 100644 index 000000000..c1d94ba4a --- /dev/null +++ b/core/os/os2/env_js.odin @@ -0,0 +1,42 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +build_env :: proc() -> (err: Error) { + return +} + +// delete_string_if_not_original :: proc(str: string) { + +// } + +@(require_results) +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + return +} + +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + return "", .Unsupported +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + +@(require_results) +_set_env :: proc(key, value: string) -> (err: Error) { + return .Unsupported +} + +@(require_results) +_unset_env :: proc(key: string) -> bool { + return true +} + +_clear_env :: proc() { + +} + +@(require_results) +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + return {}, .Unsupported +} diff --git a/core/os/os2/errors_js.odin b/core/os/os2/errors_js.odin new file mode 100644 index 000000000..c92d36736 --- /dev/null +++ b/core/os/os2/errors_js.odin @@ -0,0 +1,13 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +_Platform_Error :: enum i32 {} + +_error_string :: proc(errno: i32) -> string { + return "" +} + +_get_platform_error :: proc(errno: _Platform_Error) -> Error { + return Platform_Error(errno) +} diff --git a/core/os/os2/file_js.odin b/core/os/os2/file_js.odin new file mode 100644 index 000000000..fd4bf347c --- /dev/null +++ b/core/os/os2/file_js.odin @@ -0,0 +1,107 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:time" + +File_Impl :: distinct rawptr + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + return nil, .Unsupported +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + return nil, .Unsupported +} + +_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { + return nil, .Unsupported +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + return .Unsupported +} + +_fd :: proc(f: ^File) -> uintptr { + return 0 +} + + +_name :: proc(f: ^File) -> string { + return "" +} + +_sync :: proc(f: ^File) -> Error { + return .Unsupported +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + return .Unsupported +} + +_remove :: proc(name: string) -> Error { + return .Unsupported +} + +_rename :: proc(old_path, new_path: string) -> Error { + return .Unsupported +} + +_link :: proc(old_name, new_name: string) -> Error { + return .Unsupported +} + +_symlink :: proc(old_name, new_name: string) -> Error { + return .Unsupported +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + return "", .Unsupported +} + +_chdir :: proc(name: string) -> Error { + return .Unsupported +} + +_fchdir :: proc(f: ^File) -> Error { + return .Unsupported +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: int) -> Error { + return .Unsupported +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + return .Unsupported +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + return .Unsupported +} + +_exists :: proc(path: string) -> bool { + return false +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + return 0, .Empty +} \ No newline at end of file diff --git a/core/os/os2/heap_js.odin b/core/os/os2/heap_js.odin new file mode 100644 index 000000000..15990b517 --- /dev/null +++ b/core/os/os2/heap_js.odin @@ -0,0 +1,7 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/path_js.odin b/core/os/os2/path_js.odin new file mode 100644 index 000000000..0c0d1424b --- /dev/null +++ b/core/os/os2/path_js.odin @@ -0,0 +1,85 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> (ok: bool) { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> (err: Error) { + return .Unsupported +} + +_mkdir_all :: proc(path: string, perm: int) -> (err: Error) { + return .Unsupported +} + +_remove_all :: proc(path: string) -> (err: Error) { + return .Unsupported +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return "", .Unsupported +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + return .Unsupported +} + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + return "", .Unsupported +} + +_are_paths_identical :: proc(a, b: string) -> bool { + return false +} + +_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { + return +} + +_is_absolute_path :: proc(path: string) -> bool { + return false +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + return "", .Unsupported +} + +_get_relative_path_handle_start :: proc(base, target: string) -> bool { + return false +} + +_get_common_path_len :: proc(base, target: string) -> int { + i := 0 + end := min(len(base), len(target)) + for j in 0..=end { + if j == end || _is_path_separator(base[j]) { + if base[i:j] == target[i:j] { + i = j + } else { + break + } + } + } + return i +} + +_split_path :: proc(path: string) -> (dir, file: string) { + i := len(path) - 1 + for i >= 0 && !_is_path_separator(path[i]) { + i -= 1 + } + if i == 0 { + return path[:i+1], path[i+1:] + } else if i > 0 { + return path[:i], path[i+1:] + } + return "", path +} \ No newline at end of file diff --git a/core/os/os2/pipe_js.odin b/core/os/os2/pipe_js.odin new file mode 100644 index 000000000..253228f86 --- /dev/null +++ b/core/os/os2/pipe_js.odin @@ -0,0 +1,14 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +_pipe :: proc() -> (r, w: ^File, err: Error) { + err = .Unsupported + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/process_js.odin b/core/os/os2/process_js.odin new file mode 100644 index 000000000..a2db3d56e --- /dev/null +++ b/core/os/os2/process_js.odin @@ -0,0 +1,87 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" +import "core:time" + + +_exit :: proc "contextless" (code: int) -> ! { + runtime.panic_contextless("exit") +} + +_get_uid :: proc() -> int { + return 0 +} + +_get_euid :: proc() -> int { + return 0 +} + +_get_gid :: proc() -> int { + return 0 +} + +_get_egid :: proc() -> int { + return 0 +} + +_get_pid :: proc() -> int { + return 0 +} + +_get_ppid :: proc() -> int { + return 0 +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + err = .Unsupported + return +} + +_process_close :: proc(process: Process) -> Error { + return .Unsupported +} + +_process_kill :: proc(process: Process) -> (err: Error) { + return .Unsupported +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/stat_js.odin b/core/os/os2/stat_js.odin new file mode 100644 index 000000000..439226490 --- /dev/null +++ b/core/os/os2/stat_js.odin @@ -0,0 +1,21 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + return {}, .Unsupported +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + return {}, .Unsupported +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + return {}, .Unsupported +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} \ No newline at end of file diff --git a/core/os/os2/temp_file_js.odin b/core/os/os2/temp_file_js.odin new file mode 100644 index 000000000..e1f2b3d95 --- /dev/null +++ b/core/os/os2/temp_file_js.odin @@ -0,0 +1,9 @@ +#+build js wasm32, js wasm64p32 +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + return "", .Mode_Not_Implemented +} \ No newline at end of file From 304f22c8af635124357c5f62772f6f09676aa761 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:16:42 +0100 Subject: [PATCH 03/59] `core:os` -> `core:os/os` for CSV, INI, and XML Also had to vendor `core:encoding/ini` into `core:os/os2` for the user directories on *nix, as it used that package to read `~/.config/user-dirs.dirs`, causing an import cycle. --- core/encoding/csv/doc.odin | 34 +++++++++++++++++-------------- core/encoding/ini/ini.odin | 21 +++++++++++-------- core/encoding/xml/xml_reader.odin | 18 ++++++++-------- core/os/os2/user_posix.odin | 1 + 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin index 50b8e3d1a..58e2a7ac5 100644 --- a/core/encoding/csv/doc.odin +++ b/core/encoding/csv/doc.odin @@ -6,7 +6,7 @@ Example: import "core:fmt" import "core:encoding/csv" - import "core:os" + import os "core:os/os2" // Requires keeping the entire CSV file in memory at once iterate_csv_from_string :: proc(filename: string) { @@ -16,14 +16,15 @@ Example: r.reuse_record_buffer = true // Without it you have to each of the fields within it defer csv.reader_destroy(&r) - csv_data, ok := os.read_entire_file(filename) - if ok { + csv_data, csv_err := os.read_entire_file(filename, context.allocator) + defer delete(csv_data) + + if csv_err == nil { csv.reader_init_with_string(&r, string(csv_data)) } else { - fmt.printfln("Unable to open file: %v", filename) + fmt.printfln("Unable to open file: %v. Error: %v", filename, csv_err) return } - defer delete(csv_data) for r, i, err in csv.iterator_next(&r) { if err != nil { /* Do something with error */ } @@ -39,16 +40,16 @@ Example: r: csv.Reader r.trim_leading_space = true r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it + r.reuse_record_buffer = true // Without it you have to delete each of the fields within it defer csv.reader_destroy(&r) handle, err := os.open(filename) + defer os.close(handle) if err != nil { - fmt.eprintfln("Error opening file: %v", filename) + fmt.eprintfln("Error %v opening file: %v", err, filename) return } - defer os.close(handle) - csv.reader_init(&r, os.stream_from_handle(handle)) + csv.reader_init(&r, handle.stream) for r, i in csv.iterator_next(&r) { for f, j in r { @@ -64,21 +65,24 @@ Example: r.trim_leading_space = true defer csv.reader_destroy(&r) - csv_data, ok := os.read_entire_file(filename) - if ok { + csv_data, csv_err := os.read_entire_file(filename, context.allocator) + defer delete(csv_data, context.allocator) + if csv_err == nil { csv.reader_init_with_string(&r, string(csv_data)) } else { - fmt.printfln("Unable to open file: %v", filename) + fmt.printfln("Unable to open file: %v. Error: %v", filename, csv_err) return } - defer delete(csv_data) records, err := csv.read_all(&r) if err != nil { /* Do something with CSV parse error */ } defer { - for rec in records { - delete(rec) + for record in records { + for field in record { + delete(field) + } + delete(record) } delete(records) } diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index a119b0f2e..644ce8937 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -1,13 +1,13 @@ // Reader and writer for a variant of the `.ini` file format with `key = value` entries in `[sections]`. package encoding_ini -import "base:runtime" -import "base:intrinsics" -import "core:strings" -import "core:strconv" -import "core:io" -import "core:os" -import "core:fmt" +import "base:runtime" +import "base:intrinsics" +import "core:strings" +import "core:strconv" +import "core:io" +import os "core:os/os2" +import "core:fmt" _ :: fmt Options :: struct { @@ -121,8 +121,11 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { - data := os.read_entire_file(path, allocator) or_return + data, data_err := os.read_entire_file(path, allocator) defer delete(data, allocator) + if data_err != nil { + return + } m, err = load_map_from_string(string(data), allocator, options) ok = err == nil defer if !ok { @@ -191,4 +194,4 @@ write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) { section_index += 1 } return -} +} \ No newline at end of file diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index 8f8fffe14..798eb3f87 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -9,13 +9,13 @@ package encoding_xml - Jeroen van Rijn: Initial implementation. */ -import "core:bytes" -import "core:encoding/entity" -import "base:intrinsics" -import "core:mem" -import "core:os" -import "core:strings" -import "base:runtime" +import "base:runtime" +import "core:bytes" +import "core:encoding/entity" +import "base:intrinsics" +import "core:mem" +import os "core:os/os2" +import "core:strings" likely :: intrinsics.expect @@ -378,8 +378,8 @@ load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handl context.allocator = allocator options := options - data, data_ok := os.read_entire_file(filename) - if !data_ok { return {}, .File_Error } + data, data_err := os.read_entire_file(filename, allocator) + if data_err != nil { return {}, .File_Error } options.flags += { .Input_May_Be_Modified } diff --git a/core/os/os2/user_posix.odin b/core/os/os2/user_posix.odin index 09134d847..fa173f129 100644 --- a/core/os/os2/user_posix.odin +++ b/core/os/os2/user_posix.odin @@ -1,6 +1,7 @@ #+build !windows package os2 +import "base:intrinsics" import "base:runtime" import "core:strings" From 1cbd60f40e974eead2b46bc5fdd732c6357dfdfb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:31:54 +0100 Subject: [PATCH 04/59] Reenable `core:encoding/ini` tests --- tests/core/encoding/ini/test_core_ini.odin | 5 ++--- tests/core/normal.odin | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin index 6e6c8152e..8c554c6b5 100644 --- a/tests/core/encoding/ini/test_core_ini.odin +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -1,8 +1,7 @@ +#+feature dynamic-literals package test_core_ini -import "base:runtime" import "core:encoding/ini" -import "core:mem/virtual" import "core:strings" import "core:testing" @@ -64,7 +63,7 @@ ini_to_string :: proc(t: ^testing.T) { testing.expectf( t, - strings.contains(str, "[LEVEL]LOG = debug"), + strings.contains(str, "[LEVEL]\nLOG = debug"), "Expected `ini.save_map_to_string` to return a string equal to \"[LEVEL]LOG = debug\", got %v", str, ) diff --git a/tests/core/normal.odin b/tests/core/normal.odin index d0889bf89..bb2e59f62 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -18,6 +18,7 @@ download_assets :: proc "contextless" () { @(require) import "encoding/cbor" @(require) import "encoding/hex" @(require) import "encoding/hxa" +@(require) import "encoding/ini" @(require) import "encoding/json" @(require) import "encoding/uuid" @(require) import "encoding/varint" From f63c2094784849ab1ebe1688558e90410af83152 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:40:07 +0100 Subject: [PATCH 05/59] Convert `core:encoding/hxa` --- core/encoding/hxa/read.odin | 8 ++++---- core/encoding/hxa/write.odin | 6 +++--- tests/core/encoding/hxa/test_core_hxa.odin | 6 ++---- tests/core/normal.odin | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index 6dde16848..04dcab817 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -1,7 +1,7 @@ package encoding_hxa import "core:fmt" -import "core:os" +import os "core:os/os2" import "core:mem" Read_Error :: enum { @@ -14,13 +14,13 @@ Read_Error :: enum { read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename, allocator, loc) - if !ok { + data, data_err := os.read_entire_file(filename, allocator) + if data_err != nil { err = .Unable_To_Read_File delete(data, allocator, loc) return } - file, err = read(data, filename, print_error, allocator, loc) + file, err = read(data, filename, print_error, allocator) file.backing = data return } diff --git a/core/encoding/hxa/write.odin b/core/encoding/hxa/write.odin index 5bb950e81..e8ef9a139 100644 --- a/core/encoding/hxa/write.odin +++ b/core/encoding/hxa/write.odin @@ -1,7 +1,7 @@ package encoding_hxa -import "core:os" -import "core:mem" +import os "core:os/os2" +import "core:mem" Write_Error :: enum { None, @@ -18,7 +18,7 @@ write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) { defer delete(buf) write_internal(&Writer{data = buf}, file) - if !os.write_entire_file(filepath, buf) { + if os.write_entire_file(filepath, buf) != nil { err =.Failed_File_Write } return diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index a8f3e94f6..7f495e161 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -9,17 +9,15 @@ import "core:testing" TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa" -import "core:os" +import os "core:os/os2" @test test_read :: proc(t: ^testing.T) { - data, _ := os.read_entire_file(TEAPOT_PATH) - // file, err := hxa.read_from_file(TEAPOT_PATH) + data, _ := os.read_entire_file(TEAPOT_PATH, context.allocator) file, err := hxa.read(data) file.backing = data file.allocator = context.allocator hxa.file_destroy(file) - // fmt.printfln("%#v", file) e :: hxa.Read_Error.None testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e) diff --git a/tests/core/normal.odin b/tests/core/normal.odin index bb2e59f62..696510ac1 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -54,4 +54,4 @@ download_assets :: proc "contextless" () { @(require) import "text/regex" @(require) import "thread" @(require) import "time" -@(require) import "unicode" +@(require) import "unicode" \ No newline at end of file From 170e314f839f013fe58e0c91a61a34c1dbcfecd2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 01:42:45 +0100 Subject: [PATCH 06/59] core:os -> core:os/os for core:image --- core/image/bmp/bmp_os.odin | 14 +- core/image/general_os.odin | 8 +- core/image/jpeg/jpeg_os.odin | 8 +- core/image/netpbm/netpbm_os.odin | 14 +- core/image/png/doc.odin | 348 ------------------------------- core/image/png/png_os.odin | 10 +- core/image/qoi/qoi_os.odin | 41 ++-- core/image/tga/tga_os.odin | 38 ++-- 8 files changed, 63 insertions(+), 418 deletions(-) delete mode 100644 core/image/png/doc.odin diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin index 70a85a784..971750fda 100644 --- a/core/image/bmp/bmp_os.odin +++ b/core/image/bmp/bmp_os.odin @@ -1,18 +1,18 @@ #+build !js package core_image_bmp -import "core:os" -import "core:bytes" +import os "core:os/os2" +import "core:bytes" load :: proc{load_from_file, load_from_bytes, load_from_context} load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) - defer delete(data) + data, data_err := os.read_entire_file(filename, allocator) + defer delete(data, allocator) - if ok { + if data_err == nil { return load_from_bytes(data, options) } else { return nil, .Unable_To_Read_File @@ -28,7 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato defer bytes.buffer_destroy(out) save_to_buffer(out, img, options) or_return - write_ok := os.write_entire_file(output, out.buf[:]) + write_err := os.write_entire_file(output, out.buf[:]) - return nil if write_ok else .Unable_To_Write_File + return nil if write_err == nil else .Unable_To_Write_File } \ No newline at end of file diff --git a/core/image/general_os.odin b/core/image/general_os.odin index 98eb5bdbe..e4de1c9a6 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -1,25 +1,23 @@ #+build !js package image -import "core:os" +import os "core:os/os2" load :: proc{ load_from_bytes, load_from_file, } - load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { - data, ok := os.read_entire_file(filename, allocator) + data, data_err := os.read_entire_file(filename, allocator) defer delete(data, allocator) - if ok { + if data_err == nil { return load_from_bytes(data, options, allocator) } else { return nil, .Unable_To_Read_File } } - which :: proc{ which_bytes, which_file, diff --git a/core/image/jpeg/jpeg_os.odin b/core/image/jpeg/jpeg_os.odin index 92c0bb447..aad172c91 100644 --- a/core/image/jpeg/jpeg_os.odin +++ b/core/image/jpeg/jpeg_os.odin @@ -1,17 +1,17 @@ #+build !js package jpeg -import "core:os" +import os "core:os/os2" load :: proc{load_from_file, load_from_bytes, load_from_context} load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) - defer delete(data) + data, data_err := os.read_entire_file(filename, allocator) + defer delete(data, allocator) - if ok { + if data_err == nil { return load_from_bytes(data, options) } else { return nil, .Unable_To_Read_File diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin index 2cf2439ac..82ad55f35 100644 --- a/core/image/netpbm/netpbm_os.odin +++ b/core/image/netpbm/netpbm_os.odin @@ -1,27 +1,25 @@ #+build !js package netpbm -import "core:os" +import os "core:os/os2" load :: proc { load_from_file, load_from_bytes, } - load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename); defer delete(data) - if !ok { + data, data_err := os.read_entire_file(filename, allocator); defer delete(data) + if data_err == nil { + return load_from_bytes(data) + } else { err = .Unable_To_Read_File return } - - return load_from_bytes(data) } - save :: proc { save_to_file, save_to_buffer, @@ -33,7 +31,7 @@ save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allo data: []byte; defer delete(data) data = save_to_buffer(img, custom_info) or_return - if ok := os.write_entire_file(filename, data); !ok { + if save_err := os.write_entire_file(filename, data); save_err != nil { return .Unable_To_Write_File } diff --git a/core/image/png/doc.odin b/core/image/png/doc.odin deleted file mode 100644 index 034a6775f..000000000 --- a/core/image/png/doc.odin +++ /dev/null @@ -1,348 +0,0 @@ -/* -Reader for `PNG` images. - -The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]]. - -Example: - package main - - import "core:image" - // import "core:image/png" - import "core:bytes" - import "core:fmt" - - // For PPM writer - import "core:mem" - import "core:os" - - main :: proc() { - track := mem.Tracking_Allocator{} - mem.tracking_allocator_init(&track, context.allocator) - - context.allocator = mem.tracking_allocator(&track) - - demo() - - if len(track.allocation_map) > 0 { - fmt.println("Leaks:") - for _, v in track.allocation_map { - fmt.printf("\t%v\n\n", v) - } - } - } - - demo :: proc() { - file: string - - options := image.Options{.return_metadata} - err: image.Error - img: ^image.Image - - file = "../../../misc/logo-slim.png" - - img, err = load(file, options) - defer destroy(img) - - if err != nil { - fmt.printf("Trying to read PNG file %v returned %v\n", file, err) - } else { - fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) - - if v, ok := img.metadata.(^image.PNG_Info); ok { - // Handle ancillary chunks as you wish. - // We provide helper functions for a few types. - for c in v.chunks { - #partial switch c.header.type { - case .tIME: - if t, t_ok := core_time(c); t_ok { - fmt.printf("[tIME]: %v\n", t) - } - case .gAMA: - if gama, gama_ok := gamma(c); gama_ok { - fmt.printf("[gAMA]: %v\n", gama) - } - case .pHYs: - if phys, phys_ok := phys(c); phys_ok { - if phys.unit == .Meter { - xm := f32(img.width) / f32(phys.ppu_x) - ym := f32(img.height) / f32(phys.ppu_y) - dpi_x, dpi_y := phys_to_dpi(phys) - fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) - fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) - fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) - } else { - fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) - } - } - case .iTXt, .zTXt, .tEXt: - res, ok_text := text(c) - if ok_text { - if c.header.type == .iTXt { - fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) - } else { - fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) - } - } - defer text_destroy(res) - case .bKGD: - fmt.printf("[bKGD] %v\n", img.background) - case .eXIf: - if res, ok_exif := exif(c); ok_exif { - /* - Other than checking the signature and byte order, we don't handle Exif data. - If you wish to interpret it, pass it to an Exif parser. - */ - fmt.printf("[eXIf] %v\n", res) - } - case .PLTE: - if plte, plte_ok := plte(c); plte_ok { - fmt.printf("[PLTE] %v\n", plte) - } else { - fmt.printf("[PLTE] Error\n") - } - case .hIST: - if res, ok_hist := hist(c); ok_hist { - fmt.printf("[hIST] %v\n", res) - } - case .cHRM: - if res, ok_chrm := chrm(c); ok_chrm { - fmt.printf("[cHRM] %v\n", res) - } - case .sPLT: - res, ok_splt := splt(c) - if ok_splt { - fmt.printf("[sPLT] %v\n", res) - } - splt_destroy(res) - case .sBIT: - if res, ok_sbit := sbit(c); ok_sbit { - fmt.printf("[sBIT] %v\n", res) - } - case .iCCP: - res, ok_iccp := iccp(c) - if ok_iccp { - fmt.printf("[iCCP] %v\n", res) - } - iccp_destroy(res) - case .sRGB: - if res, ok_srgb := srgb(c); ok_srgb { - fmt.printf("[sRGB] Rendering intent: %v\n", res) - } - case: - type := c.header.type - name := chunk_type_to_name(&type) - fmt.printf("[%v]: %v\n", name, c.data) - } - } - } - } - - fmt.printf("Done parsing metadata.\n") - - if err == nil && .do_not_decompress_image not_in options && .info not_in options { - if ok := write_image_as_ppm("out.ppm", img); ok { - fmt.println("Saved decoded image.") - } else { - fmt.println("Error saving out.ppm.") - fmt.println(img) - } - } - } - - // Crappy PPM writer used during testing. Don't use in production. - write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { - - _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { - if v, ok := bg.?; ok { - res = v - } else { - if high { - l := u16(30 * 256 + 30) - - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{l, 0, l} - } else { - res = [3]u16{l >> 1, 0, l >> 1} - } - } else { - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{30, 30, 30} - } else { - res = [3]u16{15, 15, 15} - } - } - } - return - } - - // profiler.timed_proc(); - using image - using os - - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - img := image - - // PBM 16-bit images are big endian - when ODIN_ENDIAN == .Little { - if img.depth == 16 { - // The pixel components are in Big Endian. Let's byteswap back. - input := mem.slice_data_cast([]u16, img.pixels.buf[:]) - output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) - #no_bounds_check for v, i in input { - output[i] = u16be(v) - } - } - } - - pix := bytes.buffer_to_bytes(&img.pixels) - - if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { - return false - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, err := open(filename, flags, mode) - if err != nil { - return false - } - defer close(fd) - - write_string(fd, - fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), - ) - - if channels == 3 { - // We don't handle transparency here... - write_ptr(fd, raw_data(pix), len(pix)) - } else { - bpp := depth == 16 ? 2 : 1 - bytes_needed := width * height * 3 * bpp - - op := bytes.Buffer{} - bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) - defer bytes.buffer_destroy(&op) - - if channels == 1 { - if depth == 16 { - assert(len(pix) == width * height * 2) - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - #no_bounds_check for len(p16) != 0 { - r := u16(p16[0]) - o16[0] = r - o16[1] = r - o16[2] = r - p16 = p16[1:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 1 { - r := pix[i] - op.buf[o ] = r - op.buf[o+1] = r - op.buf[o+2] = r - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 2 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - - bgcol := img.background - - #no_bounds_check for len(p16) != 0 { - r := f64(u16(p16[0])) - bg: f64 - if bgcol != nil { - v := bgcol.([3]u16)[0] - bg = f64(v) - } - a := f64(u16(p16[1])) / 65535.0 - l := (a * r) + (1 - a) * bg - - o16[0] = u16(l) - o16[1] = u16(l) - o16[2] = u16(l) - - p16 = p16[2:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 2 { - r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 - c := u8(f32(r) * a1) - op.buf[o ] = c - op.buf[o+1] = c - op.buf[o+2] = c - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 4 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16be, pix) - o16 := mem.slice_data_cast([]u16be, op.buf[:]) - - #no_bounds_check for len(p16) != 0 { - - bg := _bg(img.background, 0, 0) - r := f32(p16[0]) - g := f32(p16[1]) - b := f32(p16[2]) - a := f32(p16[3]) / 65535.0 - - lr := (a * r) + (1 - a) * f32(bg[0]) - lg := (a * g) + (1 - a) * f32(bg[1]) - lb := (a * b) + (1 - a) * f32(bg[2]) - - o16[0] = u16be(lr) - o16[1] = u16be(lg) - o16[2] = u16be(lb) - - p16 = p16[4:] - o16 = o16[3:] - } - } else { - o := 0 - - for i := 0; i < len(pix); i += 4 { - - x := (i / 4) % width - y := i / width / 4 - - _b := _bg(img.background, x, y, false) - bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} - - r := f32(pix[i]) - g := f32(pix[i+1]) - b := f32(pix[i+2]) - a := f32(pix[i+3]) / 255.0 - - lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) - lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) - lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) - op.buf[o ] = lr - op.buf[o+1] = lg - op.buf[o+2] = lb - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else { - return false - } - } - return true - } -*/ -package png diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin index 8e0706206..c6a88fa52 100644 --- a/core/image/png/png_os.odin +++ b/core/image/png/png_os.odin @@ -1,19 +1,19 @@ #+build !js package png -import "core:os" +import os "core:os/os2" load :: proc{load_from_file, load_from_bytes, load_from_context} load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) - defer delete(data) + data, data_err := os.read_entire_file(filename, allocator) + defer delete(data, allocator) - if ok { + if data_err == nil { return load_from_bytes(data, options) } else { return nil, .Unable_To_Read_File } -} +} \ No newline at end of file diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin index c85fdd839..a65527d09 100644 --- a/core/image/qoi/qoi_os.odin +++ b/core/image/qoi/qoi_os.odin @@ -1,12 +1,26 @@ #+build !js package qoi -import "core:os" -import "core:bytes" +import os "core:os/os2" +import "core:bytes" + +load :: proc{load_from_file, load_from_bytes, load_from_context} + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + data, data_err := os.read_entire_file(filename, allocator) + defer delete(data, allocator) + + if data_err == nil { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} save :: proc{save_to_buffer, save_to_file} - save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { context.allocator = allocator @@ -14,24 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato defer bytes.buffer_destroy(out) save_to_buffer(out, img, options) or_return - write_ok := os.write_entire_file(output, out.buf[:]) + write_err := os.write_entire_file(output, out.buf[:]) - return nil if write_ok else .Unable_To_Write_File -} - - -load :: proc{load_from_file, load_from_bytes, load_from_context} - - -load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { - context.allocator = allocator - - data, ok := os.read_entire_file(filename) - defer delete(data) - - if ok { - return load_from_bytes(data, options) - } else { - return nil, .Unable_To_Read_File - } + return nil if write_err == nil else .Unable_To_Write_File } \ No newline at end of file diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin index a78998105..2c103b34a 100644 --- a/core/image/tga/tga_os.odin +++ b/core/image/tga/tga_os.odin @@ -1,8 +1,23 @@ #+build !js package tga -import "core:os" -import "core:bytes" +import os "core:os/os2" +import "core:bytes" + +load :: proc{load_from_file, load_from_bytes, load_from_context} + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + data, data_err := os.read_entire_file(filename, allocator) + defer delete(data) + + if data_err == nil { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} save :: proc{save_to_buffer, save_to_file} @@ -13,22 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato defer bytes.buffer_destroy(out) save_to_buffer(out, img, options) or_return - write_ok := os.write_entire_file(output, out.buf[:]) + write_err := os.write_entire_file(output, out.buf[:]) - return nil if write_ok else .Unable_To_Write_File -} - -load :: proc{load_from_file, load_from_bytes, load_from_context} - -load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { - context.allocator = allocator - - data, ok := os.read_entire_file(filename) - defer delete(data) - - if ok { - return load_from_bytes(data, options) - } else { - return nil, .Unable_To_Read_File - } + return nil if write_err == nil else .Unable_To_Write_File } \ No newline at end of file From 9c1a9c80f8cd16b7ad57ca672eccf8f020cecbb5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 02:07:03 +0100 Subject: [PATCH 07/59] core:os -> core:os/os for core:math/big --- core/math/big/radix.odin | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index 9c87440fc..c0ea8cb79 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -15,9 +15,9 @@ package math_big - Also look at extracting and splatting several digits at once. */ -import "base:intrinsics" -import "core:mem" -import "core:os" +import "base:intrinsics" +import "core:mem" +import os "core:os/os2" /* This version of `itoa` allocates on behalf of the caller. The caller must free the string. @@ -407,11 +407,10 @@ internal_int_read_from_ascii_file :: proc(a: ^Int, filename: string, radix := i8 For now, we'll read the entire file. Eventually we'll replace this with a copy that duplicates the logic of `atoi` so we don't need to read the entire file. */ - - res, ok := os.read_entire_file(filename, allocator) + res, res_err := os.read_entire_file(filename, allocator) defer delete(res, allocator) - if !ok { + if res_err != nil { return .Cannot_Read_File } @@ -441,8 +440,8 @@ internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8( len = l, } - ok := os.write_entire_file(filename, data, truncate=true) - return nil if ok else .Cannot_Write_File + write_err := os.write_entire_file(filename, data, truncate=true) + return nil if write_err == nil else .Cannot_Write_File } /* From a86ec959441dc9433074dba03373f53b6f05622a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 02:39:19 +0100 Subject: [PATCH 08/59] Address wasi errors --- core/os/os2/dir_wasi.odin | 1 - core/os/os2/file_wasi.odin | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/dir_wasi.odin b/core/os/os2/dir_wasi.odin index 61c005674..9804f07fd 100644 --- a/core/os/os2/dir_wasi.odin +++ b/core/os/os2/dir_wasi.odin @@ -1,7 +1,6 @@ #+private package os2 -import "base:runtime" import "core:slice" import "base:intrinsics" import "core:sys/wasm/wasi" diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin index fc37ef9f2..31153ae31 100644 --- a/core/os/os2/file_wasi.odin +++ b/core/os/os2/file_wasi.odin @@ -1,3 +1,4 @@ +#+feature global-context #+private package os2 From 4eab15130f1462f564f13f9a98ea54545088b411 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:43:55 +0100 Subject: [PATCH 09/59] Add updated PNG example --- core/image/png/example.odin | 136 ++++++++++++++++++++++++++++++++++++ core/image/png/png.odin | 2 + 2 files changed, 138 insertions(+) create mode 100644 core/image/png/example.odin diff --git a/core/image/png/example.odin b/core/image/png/example.odin new file mode 100644 index 000000000..cab6e4de1 --- /dev/null +++ b/core/image/png/example.odin @@ -0,0 +1,136 @@ +#+build ignore +package png_example + +import "core:image" +import "core:image/png" +import "core:image/tga" +import "core:fmt" +import "core:mem" + +demo :: proc() { + options := image.Options{.return_metadata} + err: image.Error + img: ^image.Image + + PNG_FILE :: ODIN_ROOT + "misc/logo-slim.png" + + img, err = png.load(PNG_FILE, options) + defer png.destroy(img) + + if err != nil { + fmt.eprintfln("Trying to read PNG file %v returned %v.", PNG_FILE, err) + } else { + fmt.printfln("Image: %vx%vx%v, %v-bit.", img.width, img.height, img.channels, img.depth) + + if v, ok := img.metadata.(^image.PNG_Info); ok { + // Handle ancillary chunks as you wish. + // We provide helper functions for a few types. + for c in v.chunks { + #partial switch c.header.type { + case .tIME: + if t, t_ok := png.core_time(c); t_ok { + fmt.printfln("[tIME]: %v", t) + } + case .gAMA: + if gama, gama_ok := png.gamma(c); gama_ok { + fmt.printfln("[gAMA]: %v", gama) + } + case .pHYs: + if phys, phys_ok := png.phys(c); phys_ok { + if phys.unit == .Meter { + xm := f32(img.width) / f32(phys.ppu_x) + ym := f32(img.height) / f32(phys.ppu_y) + dpi_x, dpi_y := png.phys_to_dpi(phys) + fmt.printfln("[pHYs] Image resolution is %v x %v pixels per meter.", phys.ppu_x, phys.ppu_y) + fmt.printfln("[pHYs] Image resolution is %v x %v DPI.", dpi_x, dpi_y) + fmt.printfln("[pHYs] Image dimensions are %v x %v meters.", xm, ym) + } else { + fmt.printfln("[pHYs] x: %v, y: %v pixels per unknown unit.", phys.ppu_x, phys.ppu_y) + } + } + case .iTXt, .zTXt, .tEXt: + res, ok_text := png.text(c) + if ok_text { + if c.header.type == .iTXt { + fmt.printfln("[iTXt] %v (%v:%v): %v", res.keyword, res.language, res.keyword_localized, res.text) + } else { + fmt.printfln("[tEXt/zTXt] %v: %v", res.keyword, res.text) + } + } + defer png.text_destroy(res) + case .bKGD: + fmt.printfln("[bKGD] %v", img.background) + case .eXIf: + if res, ok_exif := png.exif(c); ok_exif { + /* + Other than checking the signature and byte order, we don't handle Exif data. + If you wish to interpret it, pass it to an Exif parser. + */ + fmt.printfln("[eXIf] %v", res) + } + case .PLTE: + if plte, plte_ok := png.plte(c); plte_ok { + fmt.printfln("[PLTE] %v", plte) + } else { + fmt.printfln("[PLTE] Error") + } + case .hIST: + if res, ok_hist := png.hist(c); ok_hist { + fmt.printfln("[hIST] %v", res) + } + case .cHRM: + if res, ok_chrm := png.chrm(c); ok_chrm { + fmt.printfln("[cHRM] %v", res) + } + case .sPLT: + res, ok_splt := png.splt(c) + if ok_splt { + fmt.printfln("[sPLT] %v", res) + } + png.splt_destroy(res) + case .sBIT: + if res, ok_sbit := png.sbit(c); ok_sbit { + fmt.printfln("[sBIT] %v", res) + } + case .iCCP: + res, ok_iccp := png.iccp(c) + if ok_iccp { + fmt.printfln("[iCCP] %v", res) + } + png.iccp_destroy(res) + case .sRGB: + if res, ok_srgb := png.srgb(c); ok_srgb { + fmt.printfln("[sRGB] Rendering intent: %v", res) + } + case: + type := c.header.type + name := png.chunk_type_to_name(&type) + fmt.printfln("[%v]: %v", name, c.data) + } + } + } + } + + fmt.printfln("Done parsing metadata.") + + if err == nil && .do_not_decompress_image not_in options && .info not_in options { + if err = tga.save("out.tga", img); err == nil { + fmt.println("Saved decoded image.") + } else { + fmt.eprintfln("Error %v saving out.ppm.", err) + } + } +} + +main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + demo() + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %m", leak.location, leak.size) + } +} \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 0170d3168..5a0913cff 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1,4 +1,6 @@ #+feature using-stmt +// Reader for `PNG` images. +// The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]]. package png /* From e094de58746e2d982b6a23e28c5db2870b45d43d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 11:13:34 +0100 Subject: [PATCH 10/59] Add `loc := #caller_location` to `read_entire_file` --- core/encoding/hxa/read.odin | 4 ++-- core/os/os2/file_util.odin | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index 04dcab817..1f1c3633e 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -14,10 +14,10 @@ Read_Error :: enum { read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator - data, data_err := os.read_entire_file(filename, allocator) + data, data_err := os.read_entire_file(filename, allocator, loc) if data_err != nil { err = .Unable_To_Read_File - delete(data, allocator, loc) + delete(data, allocator) return } file, err = read(data, filename, print_error, allocator) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index c2cf7c121..f81dc2190 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -156,9 +156,12 @@ read_entire_file :: proc{ */ @(require_results) read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) { - f := open(name) or_return + f, ferr := open(name) + if ferr != nil { + return nil, ferr + } defer close(f) - return read_entire_file_from_file(f, allocator, loc) + return read_entire_file_from_file(f=f, allocator=allocator, loc=loc) } /* From 02477b25264659f10889ce84e57a6603d9d613a2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 11:16:56 +0100 Subject: [PATCH 11/59] eprintf --- core/encoding/csv/doc.odin | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin index 58e2a7ac5..c6dae3005 100644 --- a/core/encoding/csv/doc.odin +++ b/core/encoding/csv/doc.odin @@ -22,7 +22,7 @@ Example: if csv_err == nil { csv.reader_init_with_string(&r, string(csv_data)) } else { - fmt.printfln("Unable to open file: %v. Error: %v", filename, csv_err) + fmt.eprintfln("Unable to open file: %v. Error: %v", filename, csv_err) return } @@ -46,7 +46,7 @@ Example: handle, err := os.open(filename) defer os.close(handle) if err != nil { - fmt.eprintfln("Error %v opening file: %v", err, filename) + fmt.eprintfln("Unable to open file: %v. Error: %v", filename, err) return } csv.reader_init(&r, handle.stream) @@ -67,12 +67,11 @@ Example: csv_data, csv_err := os.read_entire_file(filename, context.allocator) defer delete(csv_data, context.allocator) - if csv_err == nil { - csv.reader_init_with_string(&r, string(csv_data)) - } else { - fmt.printfln("Unable to open file: %v. Error: %v", filename, csv_err) + if err != nil { + fmt.eprintfln("Unable to open file: %v. Error: %v", filename, csv_err) return } + csv.reader_init_with_string(&r, string(csv_data)) records, err := csv.read_all(&r) if err != nil { /* Do something with CSV parse error */ } From 456f9b17edd7104ebd6d938aa565b59023be7f2d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 14:20:28 +0100 Subject: [PATCH 12/59] `core:os` -> `core:os/os2` in `core:terminal` --- core/terminal/terminal.odin | 8 ++++---- core/terminal/terminal_js.odin | 4 +--- core/terminal/terminal_posix.odin | 13 ++++++++----- core/terminal/terminal_windows.odin | 12 +++++++----- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin index 37fdaff36..d24b963d3 100644 --- a/core/terminal/terminal.odin +++ b/core/terminal/terminal.odin @@ -1,7 +1,7 @@ // Interaction with the command line interface (`CLI`) of the system. package terminal -import "core:os" +import os "core:os/os2" /* This describes the range of colors that a terminal is capable of supporting. @@ -15,14 +15,14 @@ Color_Depth :: enum { } /* -Returns true if the file `handle` is attached to a terminal. +Returns true if the `File` is attached to a terminal. This is normally true for `os.stdout` and `os.stderr` unless they are redirected to a file. */ @(require_results) -is_terminal :: proc(handle: os.Handle) -> bool { - return _is_terminal(handle) +is_terminal :: proc(f: ^os.File) -> bool { + return _is_terminal(f) } /* diff --git a/core/terminal/terminal_js.odin b/core/terminal/terminal_js.odin index 4dcd4465e..78c6c240f 100644 --- a/core/terminal/terminal_js.odin +++ b/core/terminal/terminal_js.odin @@ -2,9 +2,7 @@ #+build js package terminal -import "core:os" - -_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { +_is_terminal :: proc "contextless" (handle: any) -> bool { return true } diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin index 8d96dd256..341b9084b 100644 --- a/core/terminal/terminal_posix.odin +++ b/core/terminal/terminal_posix.odin @@ -2,12 +2,15 @@ #+build linux, darwin, netbsd, openbsd, freebsd, haiku package terminal -import "base:runtime" -import "core:os" -import "core:sys/posix" +import "base:runtime" +import os "core:os/os2" +import "core:sys/posix" -_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { - return bool(posix.isatty(posix.FD(handle))) +_is_terminal :: proc "contextless" (f: ^os.File) -> bool { + context = runtime.default_context() + fd := os.fd(f) + is_tty := posix.isatty(posix.FD(fd)) + return bool(is_tty) } _init_terminal :: proc "contextless" () { diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index 6d5f98a1f..d1ade13f3 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -1,12 +1,14 @@ #+private package terminal -import "base:runtime" -import "core:os" -import "core:sys/windows" +import "base:runtime" +import os "core:os/os2" +import "core:sys/windows" -_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { - is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR +_is_terminal :: proc "contextless" (f: ^os.File) -> bool { + context = runtime.default_context() + fd := os.fd(f) + is_tty := windows.GetFileType(windows.HANDLE(fd)) == windows.FILE_TYPE_CHAR return is_tty } From 47786deddc32f00243779995ff3a9e8f52713cf2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:01:25 +0100 Subject: [PATCH 13/59] In the middle of porting core:testing --- core/fmt/fmt_js.odin | 30 +++++++++++------------ core/fmt/fmt_os.odin | 34 ++++++++++++++------------- core/log/file_console_logger.odin | 34 +++++++++++++-------------- core/os/os2/file.odin | 7 +++++- core/os/os2/file_js.odin | 3 +++ core/os/os2/file_linux.odin | 12 ++++++++++ core/os/os2/file_posix.odin | 6 +++++ core/os/os2/file_posix_darwin.odin | 2 +- core/os/os2/file_wasi.odin | 4 ++++ core/os/os2/file_windows.odin | 5 ++++ core/os/os2/process.odin | 15 ++++++++++++ core/os/os2/process_freebsd.odin | 33 ++++++++++++++++++++++++++ core/os/os2/process_js.odin | 8 +++++++ core/os/os2/process_linux.odin | 18 ++++++++++++++ core/os/os2/process_netbsd.odin | 28 ++++++++++++++++++++++ core/os/os2/process_openbsd.odin | 20 ++++++++++++++++ core/os/os2/process_posix_darwin.odin | 29 +++++++++++++++++++++-- core/os/os2/process_wasi.odin | 8 +++++++ core/os/os2/process_windows.odin | 30 +++++++++++++++++++++++ core/os/os_darwin.odin | 2 +- core/terminal/internal.odin | 8 +++---- core/terminal/terminal_posix.odin | 6 +---- core/terminal/terminal_windows.odin | 5 +--- core/testing/runner.odin | 8 +++---- core/testing/signal_handler_libc.odin | 10 ++++---- 25 files changed, 289 insertions(+), 76 deletions(-) create mode 100644 core/os/os2/process_freebsd.odin create mode 100644 core/os/os2/process_netbsd.odin create mode 100644 core/os/os2/process_openbsd.odin diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index 4ec6bd9a8..de9e12cdc 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,9 +1,9 @@ #+build js package fmt -import "core:bufio" -import "core:io" -import "core:os" +import "core:bufio" +import "core:io" +import os "core:os/os2" foreign import "odin_env" @@ -34,52 +34,52 @@ stderr := io.Writer{ } @(private="file") -fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { - switch fd { - case 1: return stdout - case 2: return stderr +fd_to_writer :: proc(f: ^os.File, loc := #caller_location) -> io.Writer { + switch { + case f == os.stdout: return stdout + case f == os.stderr: return stderr case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc) } } // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) w := bufio.writer_to_writer(&b) return wprint(w, ..args, sep=sep, flush=flush) } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) w := bufio.writer_to_writer(&b) return wprintln(w, ..args, sep=sep, flush=flush) } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { +fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:]) + bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) w := bufio.writer_to_writer(&b) return wprintf(w, fmt, ..args, flush=flush, newline=newline) } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { - return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc) +fprintfln :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { + return fprintf(f, fmt, ..args, flush=flush, newline=true, loc=loc) } // print formats using the default print settings and writes to stdout diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index a481061f1..1f292c9e4 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -3,64 +3,66 @@ #+build !orca package fmt -import "base:runtime" -import "core:os" -import "core:io" -import "core:bufio" +import "base:runtime" +import os "core:os/os2" +import "core:io" +import "core:bufio" + +// NOTE(Jeroen): The other option is to deprecate `fprint*` and make it an alias for `wprint*`, using File.stream directly. // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) + bufio.writer_init_with_buf(&b, f.stream, buf[:]) w := bufio.writer_to_writer(&b) return wprint(w, ..args, sep=sep, flush=flush) } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) + bufio.writer_init_with_buf(&b, f.stream, buf[:]) w := bufio.writer_to_writer(&b) return wprintln(w, ..args, sep=sep, flush=flush) } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false) -> int { +fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := false) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) + bufio.writer_init_with_buf(&b, f.stream, buf[:]) w := bufio.writer_to_writer(&b) return wprintf(w, fmt, ..args, flush=flush, newline=newline) } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true) -> int { - return fprintf(fd, fmt, ..args, flush=flush, newline=true) +fprintfln :: proc(f: ^os.File, fmt: string, args: ..any, flush := true) -> int { + return fprintf(f, fmt, ..args, flush=flush, newline=true) } -fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { +fprint_type :: proc(f: ^os.File, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) + bufio.writer_init_with_buf(&b, f.stream, buf[:]) w := bufio.writer_to_writer(&b) return wprint_type(w, info, flush=flush) } -fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: io.Error) { +fprint_typeid :: proc(f: ^os.File, id: typeid, flush := true) -> (n: int, err: io.Error) { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) + bufio.writer_init_with_buf(&b, f.stream, buf[:]) w := bufio.writer_to_writer(&b) return wprint_typeid(w, id, flush=flush) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index f0acc8a22..a9b073c18 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -2,13 +2,13 @@ #+build !orca package log -import "base:runtime" -import "core:fmt" -import "core:strings" -import "core:os" -import "core:terminal" -import "core:terminal/ansi" -import "core:time" +import "base:runtime" +import "core:fmt" +import "core:strings" +import os "core:os/os2" +import "core:terminal" +import "core:terminal/ansi" +import "core:time" Level_Headers := [?]string{ 0..<10 = "[DEBUG] --- ", @@ -35,7 +35,7 @@ Default_File_Logger_Opts :: Options{ File_Console_Logger_Data :: struct { - file_handle: os.Handle, + file_handle: ^os.File, ident: string, } @@ -66,16 +66,16 @@ init_standard_stream_status :: proc "contextless" () { } } -create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { +create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { data := new(File_Console_Logger_Data, allocator) - data.file_handle = h + data.file_handle = f data.ident = ident return Logger{file_logger_proc, data, lowest, opt} } destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { data := cast(^File_Console_Logger_Data)log.data - if data.file_handle != os.INVALID_HANDLE { + if data.file_handle != nil { os.close(data.file_handle) } free(data, allocator) @@ -83,7 +83,7 @@ destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { data := new(File_Console_Logger_Data, allocator) - data.file_handle = os.INVALID_HANDLE + data.file_handle = nil data.ident = ident return Logger{console_logger_proc, data, lowest, opt} } @@ -93,7 +93,7 @@ destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { } @(private) -_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) { +_file_console_logger_proc :: proc(h: ^os.File, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) { backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) @@ -106,9 +106,7 @@ _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, tex do_location_header(options, &buf, location) if .Thread_Id in options { - // NOTE(Oskar): not using context.thread_id here since that could be - // incorrect when replacing context for a thread. - fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) + fmt.sbprintf(&buf, "[{}] ", os.get_current_thread_id()) } if ident != "" { @@ -126,7 +124,7 @@ file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, option console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { options := options data := cast(^File_Console_Logger_Data)logger_data - h: os.Handle = --- + h: ^os.File = nil if level < Level.Error { h = os.stdout options -= global_subtract_stdout_options @@ -216,4 +214,4 @@ do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #ca } fmt.sbprint(buf, "] ") -} +} \ No newline at end of file diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 33446726e..bf7ebaeb5 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -538,6 +538,11 @@ is_directory :: proc(path: string) -> bool { /* `copy_file` copies a file from `src_path` to `dst_path` and returns an error if any was encountered. */ +@(require_results) +is_tty :: proc "contextless" (f: ^File) -> bool { + return _is_tty(f) +} + copy_file :: proc(dst_path, src_path: string) -> Error { when #defined(_copy_file_native) { return _copy_file_native(dst_path, src_path) @@ -562,4 +567,4 @@ _copy_file :: proc(dst_path, src_path: string) -> Error { _, err := io.copy(to_writer(dst), to_reader(src)) return err -} +} \ No newline at end of file diff --git a/core/os/os2/file_js.odin b/core/os/os2/file_js.odin index fd4bf347c..492e3557f 100644 --- a/core/os/os2/file_js.odin +++ b/core/os/os2/file_js.odin @@ -29,6 +29,9 @@ _fd :: proc(f: ^File) -> uintptr { return 0 } +_is_tty :: proc "contextless" (f: ^File) -> bool { + return true +} _name :: proc(f: ^File) -> string { return "" diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 924251dfc..f5f2ebdd7 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -6,6 +6,7 @@ import "core:io" import "core:time" import "core:sync" import "core:sys/linux" +import "core:sys/posix" // Most implementations will EINVAL at some point when doing big writes. // In practice a read/write call would probably never read/write these big buffers all at once, @@ -174,6 +175,17 @@ _fd :: proc(f: ^File) -> uintptr { return uintptr(impl.fd) } +_is_tty :: proc "contextless" (f: ^File) -> bool { + if f == nil || f.impl == nil { + return false + } + impl := (^File_Impl)(f.impl) + + // TODO: Replace `posix.isatty` with `tcgetattr(fd, &termios) == 0` + is_tty := posix.isatty(posix.FD(impl.fd)) + return bool(is_tty) +} + _name :: proc(f: ^File) -> string { return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index cdc8e491a..1830c8509 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -161,6 +161,12 @@ __fd :: proc(f: ^File) -> posix.FD { return -1 } +is_tty :: proc "contextless" (f: ^File) -> bool { + fd := _fd(f) + is_tty := posix.isatty(posix.FD(fd)) + return bool(is_tty) +} + _name :: proc(f: ^File) -> string { if f != nil && f.impl != nil { return (^File_Impl)(f.impl).name diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin index aed3e56f5..521fb345b 100644 --- a/core/os/os2/file_posix_darwin.odin +++ b/core/os/os2/file_posix_darwin.odin @@ -43,4 +43,4 @@ _copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) { } return -} +} \ No newline at end of file diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin index 31153ae31..1333ecf20 100644 --- a/core/os/os2/file_wasi.odin +++ b/core/os/os2/file_wasi.odin @@ -274,6 +274,10 @@ __fd :: proc(f: ^File) -> wasi.fd_t { return -1 } +_is_tty :: proc "contextless" (f: ^File) -> bool { + return false +} + _name :: proc(f: ^File) -> string { if f != nil && f.impl != nil { return (^File_Impl)(f.impl).name diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 6f29d151c..0e3448dd7 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -246,6 +246,11 @@ _fd :: proc "contextless" (f: ^File) -> uintptr { return uintptr((^File_Impl)(f.impl).fd) } +_is_tty :: proc "contextless" (f: ^File) -> bool { + fd := _fd(f) + return win32.GetFileType(win32.HANDLE(fd)) == win32.FILE_TYPE_CHAR +} + _destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 201d4f6e7..e4fecf2a5 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -120,6 +120,21 @@ get_ppid :: proc() -> int { return _get_ppid() } +/* +Obtain the current thread id +*/ +@(require_results) +get_current_thread_id :: proc "contextless" () -> int { + return _get_current_thread_id() +} + +/* +Return the number of cores +*/ +get_processor_core_count :: proc() -> int { + return _get_processor_core_count() +} + /* Obtain ID's of all processes running in the system. */ diff --git a/core/os/os2/process_freebsd.odin b/core/os/os2/process_freebsd.odin new file mode 100644 index 000000000..77f632192 --- /dev/null +++ b/core/os/os2/process_freebsd.odin @@ -0,0 +1,33 @@ +#+private +#+build freebsd + +foreign import libc "system:c" +foreign import dl "system:dl" + +foreign libc { + @(link_name="sysctlbyname") + _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- +} + +foreign dl { + @(link_name="pthread_getthreadid_np") + pthread_getthreadid_np :: proc() -> c.int --- +} + +@(require_results) +_get_current_thread_id :: proc "contextless" () -> int { + return int(pthread_getthreadid_np()) +} + +@(require_results) +_get_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} \ No newline at end of file diff --git a/core/os/os2/process_js.odin b/core/os/os2/process_js.odin index a2db3d56e..a59a79d45 100644 --- a/core/os/os2/process_js.odin +++ b/core/os/os2/process_js.odin @@ -34,6 +34,14 @@ _get_ppid :: proc() -> int { return 0 } +_get_current_thread_id :: proc "contextless" () -> int { + return 0 +} + +_get_processor_core_count :: proc() -> int { + return 1 +} + _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { err = .Unsupported return diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 197693dc3..4afd9f3fc 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -5,12 +5,20 @@ package os2 import "base:runtime" import "base:intrinsics" +import "core:c" import "core:time" import "core:slice" import "core:strings" import "core:strconv" +import "core:sys/unix" import "core:sys/linux" +foreign import libc "system:c" + +foreign libc { + @(link_name="get_nprocs") _unix_get_nprocs :: proc() -> c.int --- +} + PIDFD_UNASSIGNED :: ~uintptr(0) @(private="package") @@ -43,6 +51,16 @@ _get_ppid :: proc() -> int { return int(linux.getppid()) } +@(private="package") +_get_current_thread_id :: proc "contextless" () -> int { + return unix.sys_gettid() +} + +@(private="package") +_get_processor_core_count :: proc() -> int { + return int(_unix_get_nprocs()) +} + @(private="package") _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) diff --git a/core/os/os2/process_netbsd.odin b/core/os/os2/process_netbsd.odin new file mode 100644 index 000000000..53e32a68c --- /dev/null +++ b/core/os/os2/process_netbsd.odin @@ -0,0 +1,28 @@ +#+private +#+build netbsd + +@(private) +foreign libc { + @(link_name="lwp_self") + _lwp_self :: proc() -> i32 --- + + @(link_name="sysctlbyname") + _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- +} + +@(require_results) +_get_current_thread_id :: proc "contextless" () -> int { + return int(lwp_self()) +} + +_get_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} \ No newline at end of file diff --git a/core/os/os2/process_openbsd.odin b/core/os/os2/process_openbsd.odin new file mode 100644 index 000000000..90f2b5412 --- /dev/null +++ b/core/os/os2/process_openbsd.odin @@ -0,0 +1,20 @@ +#+private +#+build openbsd + +@(default_calling_convention="c") +foreign libc { + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- +} + +@(require_results) +_get_current_thread_id :: proc "contextless" () -> int { + return _unix_getthrid() +} + +_SC_NPROCESSORS_ONLN :: 503 + +@(private, require_results) +_get_processor_core_count :: proc() -> int { + return int(_sysconf(_SC_NPROCESSORS_ONLN)) +} \ No newline at end of file diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index f655d42a9..d0be228bb 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -10,14 +10,39 @@ import "core:sys/posix" import "core:sys/unix" import "core:time" -foreign import lib "system:System" +foreign import libc "system:System" -foreign lib { +foreign libc { sysctl :: proc "c" ( name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^uint, newp: rawptr, newlen: uint, ) -> posix.result --- + + @(link_name="sysctlbyname") + _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- +} + +_get_current_thread_id :: proc "contextless" () -> int { + tid: u64 + // NOTE(Oskar): available from OSX 10.6 and iOS 3.2. + // For older versions there is `syscall(SYS_thread_selfid)`, but not really + // the same thing apparently. + foreign pthread { pthread_threadid_np :: proc "c" (rawptr, ^u64) -> c.int --- } + pthread_threadid_np(nil, &tid) + return int(tid) +} + +_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 } _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin index 52fdb1680..efb2c0228 100644 --- a/core/os/os2/process_wasi.odin +++ b/core/os/os2/process_wasi.odin @@ -30,6 +30,14 @@ _get_ppid :: proc() -> int { return 0 } +_get_current_thread_id :: proc "contextless" () -> int { + return 0 +} + +_get_processor_core_count :: proc() -> int { + return 1 +} + _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { err = .Unsupported return diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 05ac9da93..b2c87c4f4 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -1,6 +1,7 @@ #+private file package os2 +import "base:intrinsics" import "base:runtime" import "core:strings" @@ -50,6 +51,35 @@ _get_ppid :: proc() -> int { return -1 } +@(private="package") +_get_current_thread_id :: proc "contextless" () -> int { + return int(win32.GetCurrentThreadId()) +} + +@(private="package") +_get_processor_core_count :: proc() -> int { + length : win32.DWORD = 0 + result := win32.GetLogicalProcessorInformation(nil, &length) + + thread_count := 0 + if !result && win32.GetLastError() == 122 && length > 0 { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + processors := make([]win32.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length, context.temp_allocator) + + result = win32.GetLogicalProcessorInformation(&processors[0], &length) + if result { + for processor in processors { + if processor.Relationship == .RelationProcessorCore { + thread := intrinsics.count_ones(processor.ProcessorMask) + thread_count += int(thread) + } + } + } + } + + return thread_count +} + @(private="package") _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index d8f7a9577..92a636255 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -598,7 +598,7 @@ foreign libc { @(link_name="fstat64") _unix_fstat :: proc(fd: Handle, stat: ^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="fsync") _unix_fsync :: proc(handle: Handle) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(handle: Handle) -> c.int --- @(link_name="dup") _unix_dup :: proc(handle: Handle) -> Handle --- @(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- diff --git a/core/terminal/internal.odin b/core/terminal/internal.odin index 47ed1818f..956726b8a 100644 --- a/core/terminal/internal.odin +++ b/core/terminal/internal.odin @@ -1,9 +1,9 @@ #+private package terminal -import "base:runtime" -import "core:os" -import "core:strings" +import "base:runtime" +import os "core:os/os2" +import "core:strings" // Reference documentation: // @@ -77,4 +77,4 @@ init_terminal :: proc "contextless" () { @(fini) fini_terminal :: proc "contextless" () { _fini_terminal() -} +} \ No newline at end of file diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin index 341b9084b..751ef85cf 100644 --- a/core/terminal/terminal_posix.odin +++ b/core/terminal/terminal_posix.odin @@ -4,13 +4,9 @@ package terminal import "base:runtime" import os "core:os/os2" -import "core:sys/posix" _is_terminal :: proc "contextless" (f: ^os.File) -> bool { - context = runtime.default_context() - fd := os.fd(f) - is_tty := posix.isatty(posix.FD(fd)) - return bool(is_tty) + return os.is_tty(f) } _init_terminal :: proc "contextless" () { diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index d1ade13f3..78d21952b 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -6,10 +6,7 @@ import os "core:os/os2" import "core:sys/windows" _is_terminal :: proc "contextless" (f: ^os.File) -> bool { - context = runtime.default_context() - fd := os.fd(f) - is_tty := windows.GetFileType(windows.HANDLE(fd)) == windows.FILE_TYPE_CHAR - return is_tty + return os.is_tty(f) } old_modes: [2]struct{ diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 03115b165..a184578a6 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -20,7 +20,7 @@ import "core:io" @require import "core:log" import "core:math/rand" import "core:mem" -import "core:os" +import os "core:os/os2" import "core:slice" @require import "core:strings" import "core:sync/chan" @@ -219,8 +219,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - stdout := io.to_writer(os.stream_from_handle(os.stdout)) - stderr := io.to_writer(os.stream_from_handle(os.stderr)) + stdout := os.stdout.stream + stderr := os.stderr.stream // The animations are only ever shown through STDOUT; // STDERR is used exclusively for logging regardless of error level. @@ -317,7 +317,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // -- Set thread count. when TEST_THREADS == 0 { - thread_count := os.processor_core_count() + thread_count := os.get_processor_core_count() } else { thread_count := max(1, TEST_THREADS) } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 74608bb48..593d2c285 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -11,11 +11,11 @@ package testing blob1807: Windows Win32 API rewrite. */ -import "base:intrinsics" -import "core:c/libc" -import "core:os" -import "core:sync" -import "core:terminal/ansi" +import "base:intrinsics" +import "core:c/libc" +import os "core:os/os2" +import "core:sync" +import "core:terminal/ansi" @(private="file") stop_runner_flag: libc.sig_atomic_t From 14021f6444248a80fdda6fb5a4e002ab94e4d9a7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:34:13 +0100 Subject: [PATCH 14/59] core:testing Darwin --- core/os/os2/file_posix.odin | 3 ++- core/os/os2/process_posix_darwin.odin | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 1830c8509..ef53bf116 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -161,7 +161,8 @@ __fd :: proc(f: ^File) -> posix.FD { return -1 } -is_tty :: proc "contextless" (f: ^File) -> bool { +_is_tty :: proc "contextless" (f: ^File) -> bool { + context = runtime.default_context() fd := _fd(f) is_tty := posix.isatty(posix.FD(fd)) return bool(is_tty) diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index d0be228bb..934d23711 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -5,12 +5,14 @@ import "base:runtime" import "base:intrinsics" import "core:bytes" +import "core:c" import "core:sys/darwin" import "core:sys/posix" import "core:sys/unix" import "core:time" -foreign import libc "system:System" +foreign import libc "system:System" +foreign import pthread "system:System" foreign libc { sysctl :: proc "c" ( @@ -33,7 +35,7 @@ _get_current_thread_id :: proc "contextless" () -> int { return int(tid) } -_processor_core_count :: proc() -> int { +_get_processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { From ab0f1aa0c43c4b71ef1b57f9b53b1e9ec16878bb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:40:04 +0100 Subject: [PATCH 15/59] BSDs --- core/os/os2/process_freebsd.odin | 3 +++ core/os/os2/process_netbsd.odin | 7 +++++-- core/os/os2/process_openbsd.odin | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/os/os2/process_freebsd.odin b/core/os/os2/process_freebsd.odin index 77f632192..8a31eb62c 100644 --- a/core/os/os2/process_freebsd.odin +++ b/core/os/os2/process_freebsd.odin @@ -1,5 +1,8 @@ #+private #+build freebsd +package os2 + +import "core:c" foreign import libc "system:c" foreign import dl "system:dl" diff --git a/core/os/os2/process_netbsd.odin b/core/os/os2/process_netbsd.odin index 53e32a68c..b46a58e58 100644 --- a/core/os/os2/process_netbsd.odin +++ b/core/os/os2/process_netbsd.odin @@ -1,9 +1,12 @@ #+private #+build netbsd +package os2 + +import "core:c" +foreign import libc "system:c" @(private) foreign libc { - @(link_name="lwp_self") _lwp_self :: proc() -> i32 --- @(link_name="sysctlbyname") @@ -12,7 +15,7 @@ foreign libc { @(require_results) _get_current_thread_id :: proc "contextless" () -> int { - return int(lwp_self()) + return int(_lwp_self()) } _get_processor_core_count :: proc() -> int { diff --git a/core/os/os2/process_openbsd.odin b/core/os/os2/process_openbsd.odin index 90f2b5412..9c6605952 100644 --- a/core/os/os2/process_openbsd.odin +++ b/core/os/os2/process_openbsd.odin @@ -1,5 +1,10 @@ #+private #+build openbsd +package os2 + +import "core:c" + +foreign import libc "system:c" @(default_calling_convention="c") foreign libc { From 586355f4ac7f308b8a304078fbb15191ffbba952 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 11:29:20 +0100 Subject: [PATCH 16/59] core:text/i18n -> core:os/os2 --- core/text/i18n/gettext.odin | 36 +++++++++------------------------ core/text/i18n/i18n.odin | 13 +++++++++++- core/text/i18n/qt_linguist.odin | 12 ++--------- 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin index a29fdc003..4aa86a35d 100644 --- a/core/text/i18n/gettext.odin +++ b/core/text/i18n/gettext.odin @@ -14,7 +14,6 @@ package i18n List of contributors: Jeroen van Rijn: Initial implementation. */ -import "core:os" import "core:strings" import "core:bytes" @@ -28,22 +27,17 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur return {}, .MO_File_Invalid } - /* - Check magic. Should be 0x950412de in native Endianness. - */ + // Check magic. Should be 0x950412de in native Endianness. native := true magic := read_u32(data, native) or_return if magic != 0x950412de { native = false - magic = read_u32(data, native) or_return - + magic = read_u32(data, native) or_return if magic != 0x950412de { return {}, .MO_File_Invalid_Signature } } - /* - We can ignore version_minor at offset 6. - */ + // We can ignore version_minor at offset 6. version_major := read_u16(data[4:]) or_return if version_major > 1 { return {}, .MO_File_Unsupported_Version } @@ -53,17 +47,13 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur if count == 0 { return {}, .Empty_Translation_Catalog } - /* - Initalize Translation, interner and optional pluralizer. - */ + // Initalize Translation, interner and optional pluralizer. translation = new(Translation) translation.pluralize = pluralizer strings.intern_init(&translation.intern, allocator, allocator) for n := u32(0); n < count; n += 1 { - /* - Grab string's original length and offset. - */ + // Grab string's original length and offset. offset := original_offset + 8 * n if len(data) < int(offset + 8) { return translation, .MO_File_Invalid } @@ -82,9 +72,7 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur key_data := data[o_offset:][:o_length] val_data := data[t_offset:][:t_length] - /* - Could be a pluralized string. - */ + // Could be a pluralized string. zero := []byte{0} keys := bytes.split(key_data, zero); defer delete(keys) vals := bytes.split(val_data, zero); defer delete(vals) @@ -138,21 +126,14 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur } parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { - context.allocator = allocator - - data, data_ok := os.read_entire_file(filename) + data := read_file(filename, allocator) or_return defer delete(data) - - if !data_ok { return {}, .File_Error } - return parse_mo_from_bytes(data, options, pluralizer, allocator) } parse_mo :: proc { parse_mo_file, parse_mo_from_bytes } -/* - Helpers. -*/ +@(private) read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) { if len(data) < size_of(u32) { return 0, .Premature_EOF } @@ -169,6 +150,7 @@ read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) { } } +@(private) read_u16 :: proc(data: []u8, native_endian := true) -> (res: u16, err: Error) { if len(data) < size_of(u16) { return 0, .Premature_EOF } diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin index b978bffc4..8b107a8cd 100644 --- a/core/text/i18n/i18n.odin +++ b/core/text/i18n/i18n.odin @@ -8,7 +8,9 @@ package i18n List of contributors: Jeroen van Rijn: Initial implementation. */ -import "core:strings" +import "base:runtime" +import os "core:os/os2" +import "core:strings" // Currently active catalog. ACTIVE: ^Translation @@ -229,4 +231,13 @@ destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) delete(catalog.k_v) strings.intern_destroy(&catalog.intern) free(catalog) +} + +@(private) +read_file :: proc(filename: string, allocator: runtime.Allocator) -> (data: []u8, err: Error) { + file_data, file_err := os.read_entire_file(filename, allocator) + if file_err != nil { + return {}, .File_Error + } + return file_data, nil } \ No newline at end of file diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 2fc5efe7b..78fe2712d 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -11,7 +11,6 @@ package i18n List of contributors: Jeroen van Rijn: Initial implementation. */ -import "core:os" import "core:encoding/xml" import "core:strings" @@ -56,9 +55,7 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI return nil, .TS_File_Parse_Error } - /* - Initalize Translation, interner and optional pluralizer. - */ + // Initalize Translation, interner and optional pluralizer. translation = new(Translation) translation.pluralize = pluralizer strings.intern_init(&translation.intern, allocator, allocator) @@ -69,7 +66,6 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI child_id := get_id(value) or_return // These should be s. - if ts.elements[child_id].ident != "context" { return translation, .TS_File_Expected_Context } @@ -159,11 +155,7 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI } parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { - context.allocator = allocator - - data, data_ok := os.read_entire_file(filename) - if !data_ok { return {}, .File_Error } - + data := read_file(filename, allocator) or_return return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) } From a4970191724182a14a4000c60c9a0677b7fa570d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:11:14 +0100 Subject: [PATCH 17/59] core:text/regex -> core:os/os2 --- core/text/regex/common/debugging.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/text/regex/common/debugging.odin b/core/text/regex/common/debugging.odin index 1a241e136..e3fcb8b7e 100644 --- a/core/text/regex/common/debugging.odin +++ b/core/text/regex/common/debugging.odin @@ -8,14 +8,14 @@ package regex_common Feoramund: Initial implementation. */ -@require import "core:os" +@require import os "core:os/os2" import "core:io" import "core:strings" ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false) when ODIN_DEBUG_REGEX { - debug_stream := os.stream_from_handle(os.stderr) + debug_stream := os.stderr.stream } write_padded_hex :: proc(w: io.Writer, #any_int n, zeroes: int) { From c0da9be4a9e216d1c050c8b5c000a34f87c5a004 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:18:12 +0100 Subject: [PATCH 18/59] core:text/table -> core:os/os2 --- core/text/table/utility.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin index 0e56fd968..99fd7d7ca 100644 --- a/core/text/table/utility.odin +++ b/core/text/table/utility.odin @@ -1,11 +1,11 @@ package text_table -import "core:io" -import "core:os" -import "core:strings" +import "core:io" +import os "core:os/os2" +import "core:strings" stdio_writer :: proc() -> io.Writer { - return io.to_writer(os.stream_from_handle(os.stdout)) + return os.stdout.stream } strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer { From 5d8de5860ba6b1234facfcd5db2d83db9a551f07 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:40:15 +0100 Subject: [PATCH 19/59] gzip -> os2 --- core/compress/gzip/doc.odin | 54 +++++++++++++++++------------------- core/compress/gzip/gzip.odin | 22 ++++++--------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/core/compress/gzip/doc.odin b/core/compress/gzip/doc.odin index c20ebc33a..82eaa6f35 100644 --- a/core/compress/gzip/doc.odin +++ b/core/compress/gzip/doc.odin @@ -2,10 +2,11 @@ A small `GZIP` unpacker. Example: - import "core:bytes" - import "core:os" - import "core:compress" - import "core:fmt" + import "core:bytes" + import os "core:os/os2" + import "core:compress" + import "core:compress/gzip" + import "core:fmt" // Small GZIP file with fextra, fname and fcomment present. @private @@ -22,7 +23,8 @@ Example: main :: proc() { // Set up output buffer. - buf := bytes.Buffer{} + buf: bytes.Buffer + defer bytes.buffer_destroy(&buf) stdout :: proc(s: string) { os.write_string(os.stdout, s) @@ -31,15 +33,13 @@ Example: os.write_string(os.stderr, s) } - args := os.args - - if len(args) < 2 { + if len(os.args) < 2 { stderr("No input file specified.\n") - err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) + err := gzip.load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) if err == nil { - stdout("Displaying test vector: ") + stdout("Displaying test vector: \"") stdout(bytes.buffer_to_string(&buf)) - stdout("\n") + stdout("\"\n") } else { fmt.printf("gzip.load returned %v\n", err) } @@ -47,35 +47,31 @@ Example: os.exit(0) } - // The rest are all files. - args = args[1:] - err: Error + for file in os.args[1:] { + err: gzip.Error - for file in args { if file == "-" { // Read from stdin - s := os.stream_from_handle(os.stdin) ctx := &compress.Context_Stream_Input{ - input = s, + input = os.stdin.stream, } - err = load(ctx, &buf) + err = gzip.load(ctx, &buf) } else { - err = load(file, &buf) + err = gzip.load(file, &buf) } - if err != nil { - if err != E_General.File_Not_Found { - stderr("File not found: ") - stderr(file) - stderr("\n") - os.exit(1) - } + switch err { + case nil: + stdout(bytes.buffer_to_string(&buf)) + case gzip.E_General.File_Not_Found: + stderr("File not found: ") + stderr(file) + stderr("\n") + os.exit(1) + case: stderr("GZIP returned an error.\n") - bytes.buffer_destroy(&buf) os.exit(2) } - stdout(bytes.buffer_to_string(&buf)) } - bytes.buffer_destroy(&buf) } */ package compress_gzip diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 7dc8120e4..644a625e7 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -14,12 +14,12 @@ package compress_gzip to be the input to a complementary TAR implementation. */ -import "core:compress/zlib" -import "core:compress" -import "core:os" -import "core:io" -import "core:bytes" -import "core:hash" +import "core:compress/zlib" +import "core:compress" +import os "core:os/os2" +import "core:io" +import "core:bytes" +import "core:hash" Magic :: enum u16le { GZIP = 0x8b << 8 | 0x1f, @@ -107,14 +107,10 @@ load :: proc{load_from_bytes, load_from_file, load_from_context} load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) - defer delete(data) + file_data, file_err := os.read_entire_file(filename, allocator) + defer delete(file_data) - err = E_General.File_Not_Found - if ok { - err = load_from_bytes(data, buf, len(data), expected_output_size) - } - return + return load_from_bytes(file_data, buf, len(file_data), expected_output_size) if file_err == nil else E_General.File_Not_Found } load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { From c2647673ec503938a302bd083f42602a5a000177 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 15:43:31 +0100 Subject: [PATCH 20/59] mem.virtual -> os2 --- core/mem/virtual/doc.odin | 15 ++++++++++++++- core/mem/virtual/file.odin | 22 ++++++++++++++-------- core/mem/virtual/virtual_linux.odin | 5 ++++- core/mem/virtual/virtual_other.odin | 7 ++++++- core/mem/virtual/virtual_posix.odin | 4 ++++ core/mem/virtual/virtual_windows.odin | 7 +++++++ 6 files changed, 49 insertions(+), 11 deletions(-) diff --git a/core/mem/virtual/doc.odin b/core/mem/virtual/doc.odin index 614e290c3..6c6ce055f 100644 --- a/core/mem/virtual/doc.odin +++ b/core/mem/virtual/doc.odin @@ -1,7 +1,6 @@ /* A platform agnostic way to reserve/commit/decommit virtual memory. - virtual.Arena usage Example: @@ -56,7 +55,21 @@ Example: vmem.arena_destroy(&arena) } +virtual.map_file usage +Example: + // Source: https://github.com/odin-lang/examples/blob/master/arena_allocator/arena_allocator.odin + import "core:fmt" + + // virtual package implements cross-platform file memory mapping + import vmem "core:mem/virtual" + + main :: proc() { + data, err := virtual.map_file_from_path(#file, {.Read}) + defer virtual.unmap_file(data) + fmt.printfln("Error: %v", err) + fmt.printfln("Data: %s", data) + } */ package mem_virtual diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin index 2f852b40c..c532335c5 100644 --- a/core/mem/virtual/file.odin +++ b/core/mem/virtual/file.odin @@ -1,6 +1,6 @@ package mem_virtual -import "core:os" +import os "core:os/os2" Map_File_Error :: enum { None, @@ -19,21 +19,20 @@ Map_File_Flags :: distinct bit_set[Map_File_Flag; u32] map_file :: proc{ map_file_from_path, - map_file_from_file_descriptor, + map_file_from_file, } map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { - fd, err := os.open(filename, os.O_RDWR) + f, err := os.open(filename, os.O_RDWR) if err != nil { return nil, .Open_Failure } - defer os.close(fd) - - return map_file_from_file_descriptor(uintptr(fd), flags) + defer os.close(f) + return map_file_from_file(f, flags) } -map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { - size, os_err := os.file_size(os.Handle(fd)) +map_file_from_file :: proc(f: ^os.File, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + size, os_err := os.file_size(f) if os_err != nil { return nil, .Stat_Failure } @@ -43,5 +42,12 @@ map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (da if size != i64(int(size)) { return nil, .Too_Large_Size } + fd := os.fd(f) return _map_file(fd, size, flags) } + +unmap_file :: proc(data: []byte) { + if raw_data(data) != nil { + _unmap_file(data) + } +} \ No newline at end of file diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index f819fbf86..144a8dc59 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -49,7 +49,6 @@ _platform_memory_init :: proc "contextless" () { assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } - _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { prot: linux.Mem_Protection if .Read in flags { @@ -66,3 +65,7 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) } return ([^]byte)(addr)[:size], nil } + +_unmap_file :: proc "contextless" (data: []byte) { + _release(raw_data(data), uint(len(data))) +} \ No newline at end of file diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index c6386e842..8a2e1a61d 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -26,8 +26,13 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) } _platform_memory_init :: proc "contextless" () { + } -_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { +_map_file :: proc "contextless" (f: any, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { return nil, .Map_Failure } + +_unmap_file :: proc "contextless" (data: []byte) { + +} \ No newline at end of file diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin index 4bb161770..6f257c385 100644 --- a/core/mem/virtual/virtual_posix.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -47,3 +47,7 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) } return ([^]byte)(addr)[:size], nil } + +_unmap_file :: proc "contextless" (data: []byte) { + _release(raw_data(data), uint(len(data))) +} \ No newline at end of file diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index 1d777af17..0866ebfa1 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -82,6 +82,8 @@ foreign Kernel32 { dwFileOffsetLow: u32, dwNumberOfBytesToMap: uint, ) -> rawptr --- + + UnmapViewOfFile :: proc(lpBaseAddress: rawptr) -> b32 --- } @(no_sanitize_address) @@ -185,3 +187,8 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) file_data := MapViewOfFile(handle, desired_access, 0, 0, uint(size)) return ([^]byte)(file_data)[:size], nil } + +@(no_sanitize_address) +_unmap_file :: proc "contextless" (data: []byte) { + UnmapViewOfFile(raw_data(data)) +} \ No newline at end of file From 8f4bcf4d31fe83f2fe9a93a0b0a8e28d11c0c882 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:48:41 +0100 Subject: [PATCH 21/59] More conflicts during rebase --- core/crypto/hash/hash_os.odin | 2 +- core/fmt/fmt_os.odin | 10 +++++----- core/net/dns.odin | 2 +- core/testing/runner.odin | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin index 32347c452..28db9c61f 100644 --- a/core/crypto/hash/hash_os.odin +++ b/core/crypto/hash/hash_os.odin @@ -16,7 +16,7 @@ hash_file_by_handle :: proc( io.Error, ) { if !load_at_once { - return hash_stream(algorithm, handle.stream, allocator) + return hash_stream(algorithm, os.to_stream(handle), allocator) } buf, err := os.read_entire_file(handle, allocator) diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 1f292c9e4..7ce945a0f 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -16,7 +16,7 @@ fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, f.stream, buf[:]) + bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:]) w := bufio.writer_to_writer(&b) return wprint(w, ..args, sep=sep, flush=flush) } @@ -27,7 +27,7 @@ fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int { b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, f.stream, buf[:]) + bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:]) w := bufio.writer_to_writer(&b) return wprintln(w, ..args, sep=sep, flush=flush) @@ -38,7 +38,7 @@ fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, f.stream, buf[:]) + bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:]) w := bufio.writer_to_writer(&b) return wprintf(w, fmt, ..args, flush=flush, newline=newline) @@ -52,7 +52,7 @@ fprint_type :: proc(f: ^os.File, info: ^runtime.Type_Info, flush := true) -> (n: b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, f.stream, buf[:]) + bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:]) w := bufio.writer_to_writer(&b) return wprint_type(w, info, flush=flush) @@ -62,7 +62,7 @@ fprint_typeid :: proc(f: ^os.File, id: typeid, flush := true) -> (n: int, err: i b: bufio.Writer defer bufio.writer_flush(&b) - bufio.writer_init_with_buf(&b, f.stream, buf[:]) + bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:]) w := bufio.writer_to_writer(&b) return wprint_typeid(w, id, flush=flush) diff --git a/core/net/dns.odin b/core/net/dns.odin index 983f82681..2d7a04896 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -23,7 +23,7 @@ package net @(require) import "base:runtime" - +import os "core:os/os2" import "core:bufio" import "core:io" import "core:math/rand" diff --git a/core/testing/runner.odin b/core/testing/runner.odin index a184578a6..8873bc973 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -219,8 +219,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - stdout := os.stdout.stream - stderr := os.stderr.stream + stdout := os.to_stream(os.stdout) + stderr := os.to_stream(os.stderr) // The animations are only ever shown through STDOUT; // STDERR is used exclusively for logging regardless of error level. From ffa94764b44763dddb32e069c92b6113b506e639 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 12:29:23 +0100 Subject: [PATCH 22/59] Fix doc tester --- tests/documentation/documentation_tester.odin | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 7b125d4e4..93246f05a 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -267,7 +267,7 @@ write_test_suite :: proc(example_tests: []Example_Test) { `#+private package documentation_verification -import "core:os" +import os "core:os/os2" import "core:mem" import "core:io" import "core:fmt" @@ -276,9 +276,11 @@ import "core:sync" import "base:intrinsics" @(private="file") -_read_pipe: os.Handle +_read_pipe: ^os.File @(private="file") -_write_pipe: os.Handle +_write_pipe: ^os.File +@(private="file") +_old_stdout: ^os.File @(private="file") _pipe_reader_semaphore: sync.Sema @(private="file") @@ -286,20 +288,20 @@ _out_data: string @(private="file") _out_buffer: [mem.Megabyte]byte @(private="file") -_bad_test_found: bool +_bad_count: int +@(private="file") +_good_count: int @(private="file") _spawn_pipe_reader :: proc() { thread.run(proc() { - stream := os.stream_from_handle(_read_pipe) - reader := io.to_reader(stream) sync.post(&_pipe_reader_semaphore) // notify thread is ready for { n_read := 0 read_to_null_byte := 0 finished_reading := false for ! finished_reading { - just_read, err := io.read(reader, _out_buffer[n_read:], &n_read); if err != .None { + just_read, err := io.read(_read_pipe.stream, _out_buffer[n_read:], &n_read); if err != .None { panic("We got an IO error!") } for b in _out_buffer[n_read - just_read: n_read] { @@ -328,11 +330,14 @@ _check :: proc(test_name: string, expected: string) { if expected != output { fmt.eprintf("Test %q got unexpected output:\n%q\n", test_name, output) fmt.eprintf("Expected:\n%q\n", expected) - _bad_test_found = true + _bad_count += 1 + } else { + _good_count += 1 } } main :: proc() { + _old_stdout = os.stdout _read_pipe, _write_pipe, _ = os.pipe() os.stdout = _write_pipe _spawn_pipe_reader() @@ -457,7 +462,8 @@ main :: proc() { strings.write_string(&test_runner, ` - if _bad_test_found { + fmt.wprintfln(_old_stdout.stream, "Passes: %v. Fails: %v", _good_count, _bad_count) + if _bad_count > 0 { fmt.eprintln("One or more tests failed") os.exit(1) } From 67db0fde4fbfaf34870b5f5ac3042c50df9a0c2c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 12:38:28 +0100 Subject: [PATCH 23/59] Port doc tester to os2 itself as well --- tests/documentation/documentation_tester.odin | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 93246f05a..55e40963d 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -1,12 +1,11 @@ package documentation_tester -import "core:os" -import "core:io" -import "core:fmt" -import "core:strings" -import "core:odin/ast" -import "core:odin/parser" -import "core:c/libc" +import os "core:os/os2" +import "core:fmt" +import "core:strings" +import "core:odin/ast" +import "core:odin/parser" +import "core:c/libc" import doc "core:odin/doc-format" Example_Test :: struct { @@ -63,10 +62,11 @@ main :: proc() { errorf("expected path to odin executable") } g_path_to_odin = os.args[1] - data, ok := os.read_entire_file("all.odin-doc") - if !ok { + data, data_err := os.read_entire_file("all.odin-doc", context.allocator) + if data_err != nil { errorf("unable to read file: all.odin-doc") } + defer delete(data) err: doc.Reader_Error g_header, err = doc.read_from_bytes(data) switch err { @@ -257,8 +257,8 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s write_test_suite :: proc(example_tests: []Example_Test) { TEST_SUITE_DIRECTORY :: "verify" - os.remove_directory(TEST_SUITE_DIRECTORY) - os.make_directory(TEST_SUITE_DIRECTORY) + os.remove_all(TEST_SUITE_DIRECTORY) + os.mkdir(TEST_SUITE_DIRECTORY) example_build := strings.builder_make() test_runner := strings.builder_make() @@ -450,13 +450,7 @@ main :: proc() { continue } defer os.close(test_file_handle) - stream := os.stream_from_handle(test_file_handle) - writer, ok := io.to_writer(stream); if ! ok { - fmt.eprintf("We could not make the writer for the path %q\n", save_path) - g_bad_doc = true - continue - } - fmt.wprintf(writer, "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:]) + fmt.wprintf(test_file_handle.stream, "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:]) fmt.println("Done") } @@ -468,7 +462,7 @@ main :: proc() { os.exit(1) } }`) - os.write_entire_file("verify/main.odin", transmute([]byte)strings.to_string(test_runner)) + _ = os.write_entire_file("verify/main.odin", transmute([]byte)strings.to_string(test_runner)) } run_test_suite :: proc() -> bool { From c265d297b5c9252db391cdf43eca079d8f634791 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 13:45:59 +0100 Subject: [PATCH 24/59] core:time/timezone -> os2 --- core/time/timezone/tz_unix.odin | 15 +++++++++------ core/time/timezone/tzif.odin | 18 ++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index 542e5c4f2..e4121266d 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -2,16 +2,16 @@ #+private package timezone -import "core:os" -import "core:strings" -import "core:path/filepath" -import "core:time/datetime" +import os "core:os/os2" +import "core:strings" +import "core:path/filepath" +import "core:time/datetime" local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { local_str, ok := os.lookup_env("TZ", allocator) if !ok { orig_localtime_path := "/etc/localtime" - path, err := os.absolute_path_from_relative(orig_localtime_path, allocator) + path, err := os.get_absolute_path(orig_localtime_path, allocator) if err != nil { // If we can't find /etc/localtime, fallback to UTC if err == .ENOENT { @@ -28,7 +28,10 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: // This is a hackaround, because FreeBSD copies rather than softlinks their local timezone file, // *sometimes* and then stores the original name of the timezone in /var/db/zoneinfo instead if path == orig_localtime_path { - data := os.read_entire_file("/var/db/zoneinfo", allocator) or_return + data, data_err := os.read_entire_file("/var/db/zoneinfo", allocator) + if data_err != nil { + return "", false + } return strings.trim_right_space(string(data)), true } diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin index 804211ef4..7a7023c6c 100644 --- a/core/time/timezone/tzif.odin +++ b/core/time/timezone/tzif.odin @@ -1,12 +1,11 @@ package timezone -import "base:intrinsics" - -import "core:slice" -import "core:strings" -import "core:os" -import "core:strconv" -import "core:time/datetime" +import "base:intrinsics" +import "core:slice" +import "core:strings" +import os "core:os/os2" +import "core:strconv" +import "core:time/datetime" // Implementing RFC8536 [https://datatracker.ietf.org/doc/html/rfc8536] @@ -70,7 +69,10 @@ tzif_data_block_size :: proc(hdr: ^TZif_Header, version: TZif_Version) -> (block load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { - tzif_data := os.read_entire_file_from_filename(filename, allocator) or_return + tzif_data, tzif_err := os.read_entire_file(filename, allocator) + if tzif_err != nil { + return nil, false + } defer delete(tzif_data, allocator) return parse_tzif(tzif_data, region_name, allocator) } From 8b3ae667f449f7f013d926b5f3d9177b2483e54f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:50:19 +0100 Subject: [PATCH 25/59] More conflicts during rebase --- core/unicode/tools/generate_entity_table.odin | 87 +++++++------------ 1 file changed, 31 insertions(+), 56 deletions(-) diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 9517b632b..02958ad26 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -1,17 +1,14 @@ package xml_example -import "core:encoding/xml" -import "core:os" +import "core:encoding/xml" +import os "core:os/os2" import path "core:path/filepath" -import "core:mem" -import "core:strings" -import "core:strconv" -import "core:slice" -import "core:fmt" +import "core:strings" +import "core:strconv" +import "core:slice" +import "core:fmt" -/* - Silent error handler for the parser. -*/ +// Silent error handler for the parser. Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {} OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", } @@ -22,7 +19,7 @@ Entity :: struct { description: string, } -generate_encoding_entity_table :: proc() { +main :: proc() { filename := path.join({ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml"}) defer delete(filename) @@ -33,14 +30,14 @@ generate_encoding_entity_table :: proc() { defer xml.destroy(doc) if err != .None { - fmt.printf("Load/Parse error: %v\n", err) + fmt.printfln("Load/Parse error: %v", err) if err == .File_Error { - fmt.printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename) + fmt.eprintfln("%q not found. Did you run \"tests\\download_assets.py\"?", filename) } os.exit(1) } - fmt.printf("\"%v\" loaded and parsed.\n", filename) + fmt.printfln("%q loaded and parsed.", filename) generated_buf: strings.Builder defer strings.builder_destroy(&generated_buf) @@ -54,7 +51,7 @@ generate_encoding_entity_table :: proc() { charlist := doc.elements[charlist_id] - fmt.printf("Found `` with %v children.\n", len(charlist.value)) + fmt.printfln("Found `` with %v children.", len(charlist.value)) entity_map: map[string]Entity defer delete(entity_map) @@ -73,7 +70,7 @@ generate_encoding_entity_table :: proc() { char := doc.elements[id] if char.ident != "character" { - fmt.eprintf("Expected ``, got `<%v>`\n", char.ident) + fmt.eprintfln("Expected ``, got `<%v>`", char.ident) os.exit(1) } @@ -90,15 +87,13 @@ generate_encoding_entity_table :: proc() { } desc, desc_ok := xml.find_child_by_ident(doc, id, "description") + assert(desc_ok) description := "" if len(doc.elements[desc].value) == 1 { description = doc.elements[desc].value[0].(string) } - /* - For us to be interested in this codepoint, it has to have at least one entity. - */ - + // For us to be interested in this codepoint, it has to have at least one entity. nth := 0 for { character_entity := xml.find_child_by_ident(doc, id, "entity", nth) or_break @@ -112,8 +107,8 @@ generate_encoding_entity_table :: proc() { } if name == "\"\"" { - fmt.printf("%#v\n", char) - fmt.printf("%#v\n", character_entity) + fmt.printfln("%#v", char) + fmt.printfln("%#v", character_entity) } if len(name) > max_name_length { longest_name = name } @@ -139,18 +134,14 @@ generate_encoding_entity_table :: proc() { } } - /* - Sort by name. - */ + // Sort by name. slice.sort(names[:]) - fmt.printf("Found %v unique `&name;` -> rune mappings.\n", count) - fmt.printf("Shortest name: %v (%v)\n", shortest_name, min_name_length) - fmt.printf("Longest name: %v (%v)\n", longest_name, max_name_length) + fmt.printfln("Found %v unique `&name;` -> rune mappings.", count) + fmt.printfln("Shortest name: %v (%v)", shortest_name, min_name_length) + fmt.printfln("Longest name: %v (%v)", longest_name, max_name_length) - /* - Generate table. - */ + // Generate table. fmt.wprintln(w, "package encoding_unicode_entity") fmt.wprintln(w, "") fmt.wprintln(w, GENERATED) @@ -158,10 +149,10 @@ generate_encoding_entity_table :: proc() { fmt.wprintf (w, TABLE_FILE_PROLOG) fmt.wprintln(w, "") - fmt.wprintf (w, "// `&%v;`\n", shortest_name) - fmt.wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length) - fmt.wprintf (w, "// `&%v;`\n", longest_name) - fmt.wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length) + fmt.wprintfln(w, "// `&%v;`", shortest_name) + fmt.wprintfln(w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v", min_name_length) + fmt.wprintfln(w, "// `&%v;`", longest_name) + fmt.wprintfln(w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v", max_name_length) fmt.wprintln(w, "") fmt.wprintln(w, @@ -198,7 +189,7 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count: } prefix = rune(v[0]) - fmt.wprintf (w, "\tcase '%v':\n", prefix) + fmt.wprintfln(w, "\tcase '%v':", prefix) fmt.wprintln(w, "\t\tswitch name {") } @@ -214,7 +205,6 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count: } else { fmt.wprintf(w, "\t\t\treturn {{%q, 0}}, 1, true\n", e.codepoints[0]) } - should_close = true } fmt.wprintln(w, "\t\t}") @@ -229,11 +219,12 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count: written := os.write_entire_file(generated_filename, transmute([]byte)strings.to_string(generated_buf)) - if written { - fmt.printf("Successfully written generated \"%v\".\n", generated_filename) + if written == nil { + fmt.printfln("Successfully written generated \"%v\".", generated_filename) } else { - fmt.printf("Failed to write generated \"%v\".\n", generated_filename) + fmt.printfln("Failed to write generated \"%v\".", generated_filename) } + // Not a library, no need to clean up. } GENERATED :: `/* @@ -274,20 +265,4 @@ is_dotted_name :: proc(name: string) -> (dotted: bool) { if r == '.' { return true} } return false -} - -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - generate_encoding_entity_table() - - if len(track.allocation_map) > 0 { - fmt.println() - for _, v in track.allocation_map { - fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) - } - } - fmt.println("Done and cleaned up!") } \ No newline at end of file From af57035fd65cda96b7ec17e965184a922fdf6e82 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 15:07:51 +0100 Subject: [PATCH 26/59] tests/core/io -> os2 --- tests/core/encoding/hxa/test_core_hxa.odin | 3 - tests/core/io/test_core_io.odin | 66 +++++----------------- 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index 7f495e161..17b3ca619 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -1,6 +1,3 @@ -// Tests "core:encoding:hxa". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/encoding/hxa/test_core_hxa.odin -out=tests/core/test_core_hxa -collection:tests=./tests package test_core_hxa import "core:encoding/hxa" diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 10c9550cb..eb4d79317 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -1,13 +1,12 @@ 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" +import "core:bufio" +import "core:bytes" +import "core:io" +import "core:log" +import os "core:os/os2" +import "core:strings" +import "core:testing" Passed_Tests :: distinct io.Stream_Mode_Set @@ -540,7 +539,7 @@ test_string_builder_stream :: proc(t: ^testing.T) { } @test -test_os_file_stream :: proc(t: ^testing.T) { +test_os2_file_stream :: proc(t: ^testing.T) { defer if !testing.failed(t) { testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) } @@ -550,51 +549,14 @@ test_os_file_stream :: proc(t: ^testing.T) { 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.. Date: Sun, 8 Feb 2026 12:51:24 +0100 Subject: [PATCH 27/59] More conflicts during rebase --- core/mem/virtual/doc.odin | 16 ++++----- core/odin/parser/parse_files.odin | 19 +++++----- core/path/filepath/match.odin | 25 ++++++------- core/path/filepath/path_windows.odin | 52 +++------------------------- core/path/filepath/walk.odin | 8 ++--- 5 files changed, 36 insertions(+), 84 deletions(-) diff --git a/core/mem/virtual/doc.odin b/core/mem/virtual/doc.odin index 6c6ce055f..249e22ee8 100644 --- a/core/mem/virtual/doc.odin +++ b/core/mem/virtual/doc.odin @@ -5,8 +5,8 @@ virtual.Arena usage Example: // Source: https://github.com/odin-lang/examples/blob/master/arena_allocator/arena_allocator.odin - import "core:fmt" - import "core:os" + import "core:fmt" + import os "core:os/os2" // virtual package implements a multi-purpose arena allocator. If you are on a // platform that does not support virtual memory, then there is also a similar @@ -26,14 +26,14 @@ Example: // See arena_init_buffer for an arena that does not use virtual memory, // instead it relies on you feeding it a buffer. - f1, f1_ok := os.read_entire_file("file1.txt", arena_alloc) - ensure(f1_ok) + f1, f1_err := os.read_entire_file("file1.txt", arena_alloc) + ensure(f1_err == nil) - f2, f2_ok := os.read_entire_file("file2.txt", arena_alloc) - ensure(f2_ok) + f2, f2_err := os.read_entire_file("file2.txt", arena_alloc) + ensure(f2_err == nil) - f3, f3_ok := os.read_entire_file("file3.txt", arena_alloc) - ensure(f3_ok) + f3, f3_err := os.read_entire_file("file3.txt", arena_alloc) + ensure(f3_err == nil) res := make([]string, 3, arena_alloc) res[0] = string(f1) diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index d4e532ec7..5e7bee923 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -1,12 +1,12 @@ package odin_parser -import "core:odin/tokenizer" -import "core:odin/ast" -import "core:path/filepath" -import "core:fmt" -import "core:os" -import "core:slice" -import "core:strings" +import "core:odin/tokenizer" +import "core:odin/ast" +import "core:path/filepath" +import "core:fmt" +import os "core:os/os2" +import "core:slice" +import "core:strings" collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { NO_POS :: tokenizer.Pos{} @@ -28,14 +28,13 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { pkg.fullpath = pkg_path for match in matches { - src: []byte fullpath, ok := filepath.abs(match) if !ok { return } - src, ok = os.read_entire_file(fullpath) - if !ok { + src, src_err := os.read_entire_file(fullpath, context.allocator) + if src_err != nil { delete(fullpath) return } diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 3eaa7c6fe..e474085ed 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -2,10 +2,10 @@ #+build !js package filepath -import "core:os" -import "core:slice" -import "core:strings" -import "core:unicode/utf8" +import os "core:os/os2" +import "core:slice" +import "core:strings" +import "core:unicode/utf8" Match_Error :: enum { None, @@ -286,28 +286,23 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont defer os.close(d) { - file_info, ferr := os.fstat(d) - defer os.file_info_delete(file_info) + file_info, ferr := os.fstat(d, allocator) + defer os.file_info_delete(file_info, allocator) if ferr != nil { return } - if !file_info.is_dir { + if file_info.type != .Directory { return } } - fis, _ := os.read_dir(d, -1) + fis, _ := os.read_dir(d, -1, allocator) slice.sort_by(fis, proc(a, b: os.File_Info) -> bool { return a.name < b.name }) - defer { - for fi in fis { - os.file_info_delete(fi) - } - delete(fis) - } + defer os.file_info_slice_delete(fis, allocator) for fi in fis { n := fi.name @@ -359,4 +354,4 @@ clean_glob_path_windows :: proc(path: string, temp_buf: []byte) -> (prefix_len: vol_len = len(path) -1 } return vol_len, path[:len(path)-1] -} +} \ No newline at end of file diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index d7549a42c..862649532 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -1,9 +1,8 @@ package filepath -import "core:strings" -import "base:runtime" -import "core:os" -import win32 "core:sys/windows" +import "core:strings" +import "base:runtime" +import os "core:os/os2" SEPARATOR :: '\\' SEPARATOR_STRING :: `\` @@ -33,53 +32,12 @@ is_UNC :: proc(path: string) -> bool { } is_abs :: proc(path: string) -> bool { - if is_reserved_name(path) { - return true - } - if len(path) > 0 && is_slash(path[0]) { - return true - } - l := volume_name_len(path) - if l == 0 { - return false - } - - path := path - path = path[l:] - if path == "" { - return false - } - return is_slash(path[0]) -} - -@(private) -temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { - ta := context.temp_allocator - - name := name - if name == "" { - name = "." - } - - p := win32.utf8_to_utf16(name, ta) - n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) - if n == 0 { - return "", os.get_last_error() - } - - buf := make([]u16, n, ta) - n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) - if n == 0 { - delete(buf) - return "", os.get_last_error() - } - - return win32.utf16_to_utf8(buf[:n], ta) + return os.is_absolute_path(path) } abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) - full_path, err := temp_full_path(path) + full_path, err := os.get_absolute_path(path, context.temp_allocator) if err != nil { return "", false } diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 05d67daf0..845ba06a0 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -2,8 +2,8 @@ #+build !js package filepath -import "core:os" -import "core:slice" +import os "core:os/os2" +import "core:slice" // Walk_Proc is the type of the procedure called for each file or directory visited by 'walk' // The 'path' parameter contains the parameter to walk as a prefix (this is the same as info.fullpath except on 'root') @@ -40,7 +40,7 @@ walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error @(private) _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) { - if !info.is_dir { + if info.type != .Directory { if info.fullpath == "" && info.name == "" { // ignore empty things return @@ -62,7 +62,7 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (e for fi in fis { err, skip_dir = _walk(fi, walk_proc, user_data) if err != nil || skip_dir { - if !fi.is_dir || !skip_dir { + if fi.type != .Directory || !skip_dir { return } } From cc50be1a6cea7587656124ad3fc0917e1ad9f737 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 23:56:13 +0100 Subject: [PATCH 28/59] Add more `filepath` to `os2` --- core/os/os2/errors.odin | 2 + core/os/os2/path.odin | 475 ++++++++++++++++++++++++++- core/os/os2/path_windows.odin | 2 +- core/path/filepath/match.odin | 324 +----------------- core/path/filepath/path.odin | 104 +----- core/path/filepath/path_windows.odin | 5 +- 6 files changed, 493 insertions(+), 419 deletions(-) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 2d959e182..508d824b3 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -24,6 +24,7 @@ General_Error :: enum u32 { Invalid_Command, Pattern_Has_Separator, + Pattern_Syntax_Error, // Indicates an error in `glob` or `match` pattern. No_HOME_Variable, Env_Var_Not_Found, @@ -74,6 +75,7 @@ error_string :: proc(ferr: Error) -> string { case .Invalid_Callback: return "invalid callback" case .Invalid_Command: return "invalid command" case .Pattern_Has_Separator: return "pattern has separator" + case .Pattern_Syntax_Error: return "glob pattern syntax error" case .No_HOME_Variable: return "no $HOME variable" case .Env_Var_Not_Found: return "environment variable not found" } diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index e12aa3c9c..55659d88f 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -1,11 +1,14 @@ package os2 import "base:runtime" - +import "core:slice" import "core:strings" +import "core:unicode/utf8" + Path_Separator :: _Path_Separator // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific +Path_Separator_Chars :: `/\` Path_List_Separator :: _Path_List_Separator // OS-Specific #assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.") @@ -315,6 +318,143 @@ split_path :: proc(path: string) -> (dir, filename: string) { return _split_path(path) } + +/* +Gets the file name and extension from a path. + +e.g. + 'path/to/name.tar.gz' -> 'name.tar.gz' + 'path/to/name.txt' -> 'name.txt' + 'path/to/name' -> 'name' + +Returns "." if the path is an empty string. +*/ +base :: proc(path: string) -> string { + if path == "" { + return "." + } + + _, file := split_path(path) + return file +} + +/* +Gets the name of a file from a path. + +The stem of a file is such that `stem(path)` + `ext(path)` = `base(path)`. + +Only the last dot is considered when splitting the file extension. +See `short_stem`. + +e.g. + 'name.tar.gz' -> 'name.tar' + 'name.txt' -> 'name' + +Returns an empty string if there is no stem. e.g: '.gitignore'. +Returns an empty string if there's a trailing path separator. +*/ +stem :: proc(path: string) -> string { + if len(path) > 0 { + if is_path_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } else if path[0] == '.' { + return "" + } + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, Path_Separator_Chars); i != -1 { + path = path[i+1:] + } + + if i := strings.last_index_byte(path, '.'); i != -1 { + return path[:i] + } + return path +} + +/* +Gets the name of a file from a path. + +The short stem is such that `short_stem(path)` + `long_ext(path)` = `base(path)`, +where `long_ext` is the extension returned by `split_filename_all`. + +The first dot is used to split off the file extension, unlike `stem` which uses the last dot. + +e.g. + 'name.tar.gz' -> 'name' + 'name.txt' -> 'name' + +Returns an empty string if there is no stem. e.g: '.gitignore'. +Returns an empty string if there's a trailing path separator. +*/ +short_stem :: proc(path: string) -> string { + s := stem(path) + if i := strings.index_byte(s, '.'); i != -1 { + return s[:i] + } + return s +} + +/* +Gets the file extension from a path, including the dot. + +The file extension is such that `stem_path(path)` + `ext(path)` = `base(path)`. + +Only the last dot is considered when splitting the file extension. +See `long_ext`. + +e.g. + 'name.tar.gz' -> '.gz' + 'name.txt' -> '.txt' + +Returns an empty string if there is no dot. +Returns an empty string if there is a trailing path separator. +*/ +ext :: proc(path: string) -> string { + for i := len(path)-1; i >= 0 && !is_path_separator(path[i]); i -= 1 { + if path[i] == '.' { + return path[i:] + } + } + return "" +} + +/* +Gets the file extension from a path, including the dot. + +The long file extension is such that `short_stem(path)` + `long_ext(path)` = `base(path)`. + +The first dot is used to split off the file extension, unlike `ext` which uses the last dot. + +e.g. + 'name.tar.gz' -> '.tar.gz' + 'name.txt' -> '.txt' + +Returns an empty string if there is no dot. +Returns an empty string if there is a trailing path separator. +*/ +long_ext :: proc(path: string) -> string { + if len(path) > 0 && is_path_separator(path[len(path) - 1]) { + // NOTE(tetra): Trailing separator + return "" + } + + // NOTE(tetra): Get the basename + path := path + if i := strings.last_index_any(path, Path_Separator_Chars); i != -1 { + path = path[i+1:] + } + + if i := strings.index_byte(path, '.'); i != -1 { + return path[i:] + } + + return "" +} + /* Join all `elems` with the system's path separator and normalize the result. @@ -460,3 +600,336 @@ split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: [] return list, nil } + +/* +`match` states whether "name" matches the shell pattern + +Pattern syntax is: + pattern: + {term} + term: + '*' matches any sequence of non-/ characters + '?' matches any single non-/ character + '[' ['^'] { character-range } ']' + character classification (cannot be empty) + c matches character c (c != '*', '?', '\\', '[') + '\\' c matches character c + + character-range + c matches character c (c != '\\', '-', ']') + '\\' c matches character c + lo '-' hi matches character c for lo <= c <= hi + +`match` requires that the pattern matches the entirety of the name, not just a substring. +The only possible error returned is `.Syntax_Error` or an allocation error. + +NOTE(bill): This is effectively the shell pattern matching system found +*/ +match :: proc(pattern, name: string) -> (matched: bool, err: Error) { + pattern, name := pattern, name + pattern_loop: for len(pattern) > 0 { + star: bool + chunk: string + star, chunk, pattern = scan_chunk(pattern) + if star && chunk == "" { + return !strings.contains(name, _Path_Separator_String), nil + } + + t, ok := match_chunk(chunk, name) or_return + + if ok && (len(t) == 0 || len(pattern) > 0) { + name = t + continue + } + + if star { + for i := 0; i < len(name) && name[i] != _Path_Separator; i += 1 { + t, ok = match_chunk(chunk, name[i+1:]) or_return + if ok { + if len(pattern) == 0 && len(t) > 0 { + continue + } + name = t + continue pattern_loop + } + } + } + + return false, nil + } + + return len(name) == 0, nil +} + +// glob returns the names of all files matching pattern or nil if there are no matching files +// The syntax of patterns is the same as "match". +// The pattern may describe hierarchical names such as /usr/*/bin (assuming '/' is a separator) +// +// glob ignores file system errors +// +glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Error) { + context.allocator = allocator + + if !has_meta(pattern) { + // TODO(bill): os.lstat on here to check for error + m := make([]string, 1) + m[0] = pattern + return m[:], nil + } + + dir, file := split_path(pattern) + + volume_len: int + temp_buf: [8]byte + volume_len, dir = _clean_glob_path(dir, temp_buf[:]) + + if !has_meta(dir[volume_len:]) { + m, e := _glob(dir, file, nil) + return m[:], e + } + + m := glob(dir) or_return + defer { + for s in m { + delete(s) + } + delete(m) + } + + dmatches := make([dynamic]string, 0, 0) + for d in m { + dmatches, err = _glob(d, file, &dmatches) + if err != nil { + break + } + } + if len(dmatches) > 0 { + matches = dmatches[:] + } + return +} + +/* + Returns leading volume name. + + e.g. + "C:\foo\bar\baz" will return "C:" on Windows. + Everything else will be "". +*/ +volume_name :: proc(path: string) -> string { + when ODIN_OS == .Windows { + return path[:_volume_name_len(path)] + } else { + return "" + } +} + +@(private="file") +scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) { + pattern := pattern + for len(pattern) > 0 && pattern[0] == '*' { + pattern = pattern[1:] + star = true + } + + in_range, i := false, 0 + + scan_loop: for i = 0; i < len(pattern); i += 1 { + switch pattern[i] { + case '\\': + when ODIN_OS != .Windows { + if i+1 < len(pattern) { + i += 1 + } + } + case '[': + in_range = true + case ']': + in_range = false + case '*': + in_range or_break scan_loop + + } + } + return star, pattern[:i], pattern[i:] +} + +@(private="file") +match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Error) { + chunk, s := chunk, s + for len(chunk) > 0 { + if len(s) == 0 { + return + } + switch chunk[0] { + case '[': + r, w := utf8.decode_rune_in_string(s) + s = s[w:] + chunk = chunk[1:] + is_negated := false + if len(chunk) > 0 && chunk[0] == '^' { + is_negated = true + chunk = chunk[1:] + } + match := false + range_count := 0 + for { + if len(chunk) > 0 && chunk[0] == ']' && range_count > 0 { + chunk = chunk[1:] + break + } + lo, hi: rune + if lo, chunk, err = get_escape(chunk); err != nil { + return + } + hi = lo + if chunk[0] == '-' { + if hi, chunk, err = get_escape(chunk[1:]); err != nil { + return + } + } + + if lo <= r && r <= hi { + match = true + } + range_count += 1 + } + if match == is_negated { + return + } + + case '?': + if s[0] == _Path_Separator { + return + } + _, w := utf8.decode_rune_in_string(s) + s = s[w:] + chunk = chunk[1:] + + case '\\': + when ODIN_OS != .Windows { + chunk = chunk[1:] + if len(chunk) == 0 { + err = .Pattern_Syntax_Error + return + } + } + fallthrough + case: + if chunk[0] != s[0] { + return + } + s = s[1:] + chunk = chunk[1:] + + } + } + return s, true, nil +} + +@(private="file") +get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Error) { + if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { + err = .Pattern_Syntax_Error + return + } + chunk := chunk + if chunk[0] == '\\' && ODIN_OS != .Windows { + chunk = chunk[1:] + if len(chunk) == 0 { + err = .Pattern_Syntax_Error + return + } + } + + w: int + r, w = utf8.decode_rune_in_string(chunk) + if r == utf8.RUNE_ERROR && w == 1 { + err = .Pattern_Syntax_Error + } + + next_chunk = chunk[w:] + if len(next_chunk) == 0 { + err = .Pattern_Syntax_Error + } + + return +} + +// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`. +_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Error) { + context.allocator = allocator + + if matches != nil { + m = matches^ + } else { + m = make([dynamic]string, 0, 0) + } + + + d := open(dir, O_RDONLY) or_return + defer close(d) + + file_info := fstat(d, allocator) or_return + defer file_info_delete(file_info, allocator) + + if file_info.type != .Directory { + return + } + + fis, _ := read_dir(d, -1, allocator) + slice.sort_by(fis, proc(a, b: File_Info) -> bool { + return a.name < b.name + }) + defer file_info_slice_delete(fis, allocator) + + for fi in fis { + matched := match(pattern, fi.name) or_return + if matched { + matched_path := join_path({dir, fi.name}, allocator) or_return + append(&m, matched_path) + } + } + return +} + +@(private) +has_meta :: proc(path: string) -> bool { + when ODIN_OS == .Windows { + CHARS :: `*?[` + } else { + CHARS :: `*?[\` + } + return strings.contains_any(path, CHARS) +} + +@(private) +_clean_glob_path :: proc(path: string, temp_buf: []byte) -> (prefix_len: int, cleaned: string) { + when ODIN_OS == .Windows { + vol_len := _volume_name_len(path) + + switch { + case path == "": + return 0, "." + case vol_len+1 == len(path) && is_path_separator(path[len(path)-1]): // /, \, C:\, C:/ + return vol_len+1, path + case vol_len == len(path) && len(path) == 2: // C: + copy(temp_buf[:], path) + temp_buf[2] = '.' + return vol_len, string(temp_buf[:3]) + } + + if vol_len >= len(path) { + vol_len = len(path) -1 + } + return vol_len, path[:len(path)-1] + } else { + switch path { + case "": + return 0, "." + case _Path_Separator_String: + return 0, path + } + return 0, path[:len(path)-1] + } +} \ No newline at end of file diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index ce3828755..f1b58c35b 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -355,4 +355,4 @@ _split_path :: proc(path: string) -> (dir, file: string) { return path[:i], path[i+1:] } return "", path -} +} \ No newline at end of file diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index e474085ed..00f5bcc3f 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -3,14 +3,6 @@ package filepath import os "core:os/os2" -import "core:slice" -import "core:strings" -import "core:unicode/utf8" - -Match_Error :: enum { - None, - Syntax_Error, -} // match states whether "name" matches the shell pattern // Pattern syntax is: @@ -34,183 +26,7 @@ Match_Error :: enum { // // NOTE(bill): This is effectively the shell pattern matching system found // -match :: proc(pattern, name: string) -> (matched: bool, err: Match_Error) { - pattern, name := pattern, name - pattern_loop: for len(pattern) > 0 { - star: bool - chunk: string - star, chunk, pattern = scan_chunk(pattern) - if star && chunk == "" { - return !strings.contains(name, SEPARATOR_STRING), .None - } - - t: string - ok: bool - t, ok, err = match_chunk(chunk, name) - - if ok && (len(t) == 0 || len(pattern) > 0) { - name = t - continue - } - if err != .None { - return - } - if star { - for i := 0; i < len(name) && name[i] != SEPARATOR; i += 1 { - t, ok, err = match_chunk(chunk, name[i+1:]) - if ok { - if len(pattern) == 0 && len(t) > 0 { - continue - } - name = t - continue pattern_loop - } - if err != .None { - return - } - } - } - - return false, .None - } - - return len(name) == 0, .None -} - - -@(private="file") -scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) { - pattern := pattern - for len(pattern) > 0 && pattern[0] == '*' { - pattern = pattern[1:] - star = true - } - - in_range, i := false, 0 - - scan_loop: for i = 0; i < len(pattern); i += 1 { - switch pattern[i] { - case '\\': - when ODIN_OS != .Windows { - if i+1 < len(pattern) { - i += 1 - } - } - case '[': - in_range = true - case ']': - in_range = false - case '*': - in_range or_break scan_loop - - } - } - return star, pattern[:i], pattern[i:] -} - -@(private="file") -match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Match_Error) { - chunk, s := chunk, s - for len(chunk) > 0 { - if len(s) == 0 { - return - } - switch chunk[0] { - case '[': - r, w := utf8.decode_rune_in_string(s) - s = s[w:] - chunk = chunk[1:] - is_negated := false - if len(chunk) > 0 && chunk[0] == '^' { - is_negated = true - chunk = chunk[1:] - } - match := false - range_count := 0 - for { - if len(chunk) > 0 && chunk[0] == ']' && range_count > 0 { - chunk = chunk[1:] - break - } - lo, hi: rune - if lo, chunk, err = get_escape(chunk); err != .None { - return - } - hi = lo - if chunk[0] == '-' { - if hi, chunk, err = get_escape(chunk[1:]); err != .None { - return - } - } - - if lo <= r && r <= hi { - match = true - } - range_count += 1 - } - if match == is_negated { - return - } - - case '?': - if s[0] == SEPARATOR { - return - } - _, w := utf8.decode_rune_in_string(s) - s = s[w:] - chunk = chunk[1:] - - case '\\': - when ODIN_OS != .Windows { - chunk = chunk[1:] - if len(chunk) == 0 { - err = .Syntax_Error - return - } - } - fallthrough - case: - if chunk[0] != s[0] { - return - } - s = s[1:] - chunk = chunk[1:] - - } - } - return s, true, .None -} - -@(private="file") -get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Error) { - if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' { - err = .Syntax_Error - return - } - chunk := chunk - if chunk[0] == '\\' && ODIN_OS != .Windows { - chunk = chunk[1:] - if len(chunk) == 0 { - err = .Syntax_Error - return - } - } - - w: int - r, w = utf8.decode_rune_in_string(chunk) - if r == utf8.RUNE_ERROR && w == 1 { - err = .Syntax_Error - } - - next_chunk = chunk[w:] - if len(next_chunk) == 0 { - err = .Syntax_Error - } - - return -} - - +match :: os.match // glob returns the names of all files matching pattern or nil if there are no matching files // The syntax of patterns is the same as "match". @@ -218,140 +34,4 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er // // glob ignores file system errors // -glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) { - context.allocator = allocator - - if !has_meta(pattern) { - // TODO(bill): os.lstat on here to check for error - m := make([]string, 1) - m[0] = pattern - return m[:], .None - } - - dir, file := split(pattern) - volume_len := 0 - when ODIN_OS == .Windows { - temp_buf: [8]byte - volume_len, dir = clean_glob_path_windows(dir, temp_buf[:]) - - } else { - dir = clean_glob_path(dir) - } - - if !has_meta(dir[volume_len:]) { - m, e := _glob(dir, file, nil) - return m[:], e - } - - m: []string - m, err = glob(dir) - if err != .None { - return - } - defer { - for s in m { - delete(s) - } - delete(m) - } - - dmatches := make([dynamic]string, 0, 0) - for d in m { - dmatches, err = _glob(d, file, &dmatches) - if err != .None { - break - } - } - if len(dmatches) > 0 { - matches = dmatches[:] - } - return -} - -// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`. -_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) { - context.allocator = allocator - - if matches != nil { - m = matches^ - } else { - m = make([dynamic]string, 0, 0) - } - - - d, derr := os.open(dir, os.O_RDONLY) - if derr != nil { - return - } - defer os.close(d) - - { - file_info, ferr := os.fstat(d, allocator) - defer os.file_info_delete(file_info, allocator) - - if ferr != nil { - return - } - if file_info.type != .Directory { - return - } - } - - - fis, _ := os.read_dir(d, -1, allocator) - slice.sort_by(fis, proc(a, b: os.File_Info) -> bool { - return a.name < b.name - }) - defer os.file_info_slice_delete(fis, allocator) - - for fi in fis { - n := fi.name - matched := match(pattern, n) or_return - if matched { - append(&m, join({dir, n})) - } - } - return -} - -@(private) -has_meta :: proc(path: string) -> bool { - when ODIN_OS == .Windows { - CHARS :: `*?[` - } else { - CHARS :: `*?[\` - } - return strings.contains_any(path, CHARS) -} - -@(private) -clean_glob_path :: proc(path: string) -> string { - switch path { - case "": - return "." - case SEPARATOR_STRING: - return path - } - return path[:len(path)-1] -} - - -@(private) -clean_glob_path_windows :: proc(path: string, temp_buf: []byte) -> (prefix_len: int, cleaned: string) { - vol_len := volume_name_len(path) - switch { - case path == "": - return 0, "." - case vol_len+1 == len(path) && is_separator(path[len(path)-1]): // /, \, C:\, C:/ - return vol_len+1, path - case vol_len == len(path) && len(path) == 2: // C: - copy(temp_buf[:], path) - temp_buf[2] = '.' - return vol_len, string(temp_buf[:3]) - } - - if vol_len >= len(path) { - vol_len = len(path) -1 - } - return vol_len, path[:len(path)-1] -} \ No newline at end of file +glob :: os.glob \ No newline at end of file diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index dbad98fa1..efc1707af 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -2,19 +2,14 @@ // To process paths such as URLs that depend on forward slashes regardless of the OS, use the slashpath package. package filepath -import "base:runtime" -import "core:strings" +import "base:runtime" +import os "core:os/os2" +import "core:strings" SEPARATOR_CHARS :: `/\` // is_separator checks whether the byte is a valid separator character -is_separator :: proc(c: byte) -> bool { - switch c { - case '/': return true - case '\\': return ODIN_OS == .Windows - } - return false -} +is_separator :: os.is_path_separator @(private) is_slash :: proc(c: byte) -> bool { @@ -23,14 +18,7 @@ is_slash :: proc(c: byte) -> bool { // Splits path immediate following the last separator; separating the path into a directory and file. // If no separator is found, `dir` will be empty and `path` set to `path`. -split :: proc(path: string) -> (dir, file: string) { - vol := volume_name(path) - i := len(path) - 1 - for i >= len(vol) && !is_separator(path[i]) { - i -= 1 - } - return path[:i+1], path[i+1:] -} +split :: os.split_path /* Returns leading volume name. @@ -123,30 +111,7 @@ volume_name_len :: proc(path: string) -> int { Returns "." if the path is an empty string. */ -base :: proc(path: string) -> string { - if path == "" { - return "." - } - - path := path - for len(path) > 0 && is_separator(path[len(path)-1]) { - path = path[:len(path)-1] - } - - path = path[volume_name_len(path):] - - i := len(path)-1 - for i >= 0 && !is_separator(path[i]) { - i -= 1 - } - if i >= 0 { - path = path[i+1:] - } - if path == "" { - return SEPARATOR_STRING - } - return path -} +base :: os.base /* Gets the name of a file from a path. @@ -163,24 +128,7 @@ base :: proc(path: string) -> string { Returns an empty string if there is no stem. e.g: '.gitignore'. Returns an empty string if there's a trailing path separator. */ -stem :: proc(path: string) -> string { - if len(path) > 0 && is_separator(path[len(path) - 1]) { - // NOTE(tetra): Trailing separator - return "" - } - - // NOTE(tetra): Get the basename - path := path - if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { - path = path[i+1:] - } - - if i := strings.last_index_byte(path, '.'); i != -1 { - return path[:i] - } - - return path -} +stem :: os.stem /* Gets the name of a file from a path. @@ -196,13 +144,7 @@ stem :: proc(path: string) -> string { Returns an empty string if there is no stem. e.g: '.gitignore'. Returns an empty string if there's a trailing path separator. */ -short_stem :: proc(path: string) -> string { - s := stem(path) - if i := strings.index_byte(s, '.'); i != -1 { - return s[:i] - } - return s -} +short_stem :: os.short_stem /* Gets the file extension from a path, including the dot. @@ -219,14 +161,7 @@ short_stem :: proc(path: string) -> string { Returns an empty string if there is no dot. Returns an empty string if there is a trailing path separator. */ -ext :: proc(path: string) -> string { - for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 { - if path[i] == '.' { - return path[i:] - } - } - return "" -} +ext :: os.ext /* Gets the file extension from a path, including the dot. @@ -242,24 +177,7 @@ ext :: proc(path: string) -> string { Returns an empty string if there is no dot. Returns an empty string if there is a trailing path separator. */ -long_ext :: proc(path: string) -> string { - if len(path) > 0 && is_separator(path[len(path) - 1]) { - // NOTE(tetra): Trailing separator - return "" - } - - // NOTE(tetra): Get the basename - path := path - if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 { - path = path[i+1:] - } - - if i := strings.index_byte(path, '.'); i != -1 { - return path[i:] - } - - return "" -} +long_ext :: os.long_ext /* Returns the shortest path name equivalent to `path` through solely lexical processing. @@ -591,4 +509,4 @@ lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) -> runtime.Allocator_Error { err := delete(lb.b) lb^ = {} return err -} +} \ No newline at end of file diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 862649532..5b81d57a0 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -41,7 +41,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { if err != nil { return "", false } - p := clean(full_path, allocator) + p, _ := clean(full_path, allocator) return p, true } @@ -68,7 +68,8 @@ join_non_empty :: proc(elems: []string, allocator := context.allocator) -> (join } s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return s = strings.concatenate({elems[0], s}, context.temp_allocator) or_return - return clean(s) + s, _ = clean(s) + return } p := strings.join(elems, SEPARATOR_STRING, context.temp_allocator) or_return From 691dc44719700add54f1ec86fbed9f9a93183855 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 Oct 2025 14:24:30 +0100 Subject: [PATCH 29/59] Add `glob` + `match` to os2 --- core/os/os2/path.odin | 30 ++++++++++++++++++++++-------- tests/core/os/os2/path.odin | 8 ++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 55659d88f..c3effe69e 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -22,6 +22,11 @@ is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) } +@(private) +is_slash :: proc(c: byte) -> bool { + return c == '\\' || c == '/' +} + mkdir :: make_directory /* @@ -668,6 +673,15 @@ match :: proc(pattern, name: string) -> (matched: bool, err: Error) { // glob ignores file system errors // glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Error) { + _split :: proc(path: string) -> (dir, file: string) { + vol := volume_name(path) + i := len(path) - 1 + for i >= len(vol) && !is_path_separator(path[i]) { + i -= 1 + } + return path[:i+1], path[i+1:] + } + context.allocator = allocator if !has_meta(pattern) { @@ -677,13 +691,14 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str return m[:], nil } - dir, file := split_path(pattern) + // NOTE(Jeroen): For `glob`, we need this version of `split`, which leaves the trailing `/` on `dir`. + dir, file := _split(pattern) - volume_len: int - temp_buf: [8]byte - volume_len, dir = _clean_glob_path(dir, temp_buf[:]) + temp_buf: [8]byte + vol_len: int + vol_len, dir = clean_glob_path(dir, temp_buf[:]) - if !has_meta(dir[volume_len:]) { + if !has_meta(dir[vol_len:]) { m, e := _glob(dir, file, nil) return m[:], e } @@ -904,10 +919,9 @@ has_meta :: proc(path: string) -> bool { } @(private) -_clean_glob_path :: proc(path: string, temp_buf: []byte) -> (prefix_len: int, cleaned: string) { +clean_glob_path :: proc(path: string, temp_buf: []byte) -> (int, string) { when ODIN_OS == .Windows { vol_len := _volume_name_len(path) - switch { case path == "": return 0, "." @@ -927,7 +941,7 @@ _clean_glob_path :: proc(path: string, temp_buf: []byte) -> (prefix_len: int, cl switch path { case "": return 0, "." - case _Path_Separator_String: + case Path_Separator_String: return 0, path } return 0, path[:len(path)-1] diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin index 7b1cb0146..1a470396b 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/os2/path.odin @@ -375,3 +375,11 @@ test_split_path_list :: proc(t: ^testing.T) { } } } + +@(test) +test_glob :: proc(t: ^testing.T) { + + + + +} \ No newline at end of file From 2f587767453eb486df7395be93554e9b5029be4c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:54:20 +0100 Subject: [PATCH 30/59] More conflicts during rebase --- core/time/timezone/tz_unix.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index e4121266d..ac6a4bb50 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -37,10 +37,11 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: // Looking for tz path (ex fmt: "UTC", "Etc/UTC" or "America/Los_Angeles") path_dir, path_file := filepath.split(path) + if path_dir == "" { return } - upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)-1]) + upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)]) if upper_path_dir == "" { return } @@ -79,7 +80,6 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r delete(local_name, allocator) return nil, true } - reg_str = local_name } defer if _reg_str == "local" { delete(reg_str, allocator) } @@ -90,7 +90,7 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r if tzdir_ok { region_path := filepath.join({tzdir_str, reg_str}, allocator) defer delete(region_path, allocator) - + if tz_reg, ok := load_tzif_file(region_path, reg_str, allocator); ok { return tz_reg, true } From 1c9e36b05ee5d0ad0fd867d71ccc6863ebc90e79 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 11:01:30 +0100 Subject: [PATCH 31/59] Fix js_wasm --- core/os/os2/file_js.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/os/os2/file_js.odin b/core/os/os2/file_js.odin index 492e3557f..91ee7f02e 100644 --- a/core/os/os2/file_js.odin +++ b/core/os/os2/file_js.odin @@ -9,7 +9,7 @@ import "core:time" File_Impl :: distinct rawptr -_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { return nil, .Unsupported } @@ -73,11 +73,11 @@ _fchdir :: proc(f: ^File) -> Error { return .Unsupported } -_fchmod :: proc(f: ^File, mode: int) -> Error { +_fchmod :: proc(f: ^File, mode: Permissions) -> Error { return .Unsupported } -_chmod :: proc(name: string, mode: int) -> Error { +_chmod :: proc(name: string, mode: Permissions) -> Error { return .Unsupported } From 5d03da8365bd17715a29e497398633fcefcbd8c7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 11:24:39 +0100 Subject: [PATCH 32/59] Start of glob test --- tests/core/os/os2/path.odin | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin index 1a470396b..26cf1c290 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/os2/path.odin @@ -3,6 +3,7 @@ package tests_core_os_os2 import os "core:os/os2" import "core:log" import "core:testing" +import "core:slice" import "core:strings" @(test) @@ -376,10 +377,33 @@ test_split_path_list :: proc(t: ^testing.T) { } } +Glob_Test :: struct { + pattern: string, + matches: []string, + err: os.Error, +} + +glob_tests := []Glob_Test{ + { + pattern = ODIN_ROOT + "tests/core/os/*/*.txt", + matches = {}, + err = nil, + }, +} + @(test) test_glob :: proc(t: ^testing.T) { + for glob in glob_tests { + files, err := os.glob(glob.pattern, context.allocator) + defer { + for file in files { + delete(file) + } + delete(files) + } + testing.expect_value(t, err, glob.err) - - - + slice.sort(files) + log.infof("files: %v", files) + } } \ No newline at end of file From 5924fb448ee13deca0fe6536cdd7f76fe5d6a583 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 12:59:35 +0100 Subject: [PATCH 33/59] Add tests for glob + match --- core/os/os2/path.odin | 10 ++++++- tests/core/os/os2/path.odin | 55 +++++++++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index c3effe69e..1d3dca5c9 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -771,6 +771,14 @@ scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) { @(private="file") match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Error) { + slash_equal :: proc(a, b: u8) -> bool { + switch a { + case '/': return b == '/' || b == '\\' + case '\\': return b == '/' || b == '\\' + case: return a == b + } + } + chunk, s := chunk, s for len(chunk) > 0 { if len(s) == 0 { @@ -831,7 +839,7 @@ match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Error) { } fallthrough case: - if chunk[0] != s[0] { + if !slash_equal(chunk[0], s[0]) { return } s = s[1:] diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin index 26cf1c290..a256b3e22 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/os2/path.odin @@ -1,5 +1,6 @@ package tests_core_os_os2 +import "core:fmt" import os "core:os/os2" import "core:log" import "core:testing" @@ -386,24 +387,62 @@ Glob_Test :: struct { glob_tests := []Glob_Test{ { pattern = ODIN_ROOT + "tests/core/os/*/*.txt", - matches = {}, - err = nil, + matches = { + ODIN_ROOT + "tests/core/os/dir/b.txt", + }, + err = {}, + }, + { + pattern = ODIN_ROOT + "tests/core/os/os2/*.odin", + matches = { + ODIN_ROOT + "tests/core/os/os2/dir.odin", + ODIN_ROOT + "tests/core/os/os2/file.odin", + ODIN_ROOT + "tests/core/os/os2/path.odin", + ODIN_ROOT + "tests/core/os/os2/process.odin", + }, + err = {}, }, } @(test) test_glob :: proc(t: ^testing.T) { + compare_matches :: proc(t: ^testing.T, pattern: string, globbed, expected: []string) { + glob_fold := make([]string, len(globbed), context.temp_allocator) + expect_fold := make([]string, len(globbed), context.temp_allocator) + + for glob, i in globbed { + // If `glob` returned a path in response to a pattern, + // then `match` should consider that path a match, too, + // irrespective of `/` versus `\` presence. + no_match_msg := fmt.tprintf("Expected os.match(%q, %q) to be `true`, got `false`", pattern, glob) + match, _ := os.match(pattern, glob) + + f, _ := strings.replace_all(glob, `\`, `/`, context.temp_allocator) + glob_fold[i] = f + testing.expect(t, match, no_match_msg) + } + + for exp, i in expected { + f, _ := strings.replace_all(exp, `\`, `/`, context.temp_allocator) + expect_fold[i] = f + } + + slice.sort(glob_fold) + slice.sort(expect_fold) + + not_equal_msg := fmt.tprintf("Expected os.glob(%q) to return %v, got %v", pattern, glob_fold, expect_fold) + testing.expect(t, slice.equal(glob_fold, expect_fold), not_equal_msg) + } + for glob in glob_tests { - files, err := os.glob(glob.pattern, context.allocator) + globbed, err := os.glob(glob.pattern, context.allocator) defer { - for file in files { + for file in globbed { delete(file) } - delete(files) + delete(globbed) } testing.expect_value(t, err, glob.err) - - slice.sort(files) - log.infof("files: %v", files) + compare_matches(t, glob.pattern, globbed, glob.matches) } } \ No newline at end of file From 0c341123cb7cd07db56def2be23865550bdef75a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:56:00 +0100 Subject: [PATCH 34/59] More conflicts during rebase --- core/os/os2/path.odin | 28 ++-- core/os/os2/path_windows.odin | 1 + core/os/os2/stat.odin | 4 + core/os/os2/stat_js.odin | 4 + core/os/os2/stat_linux.odin | 4 + core/os/os2/stat_posix.odin | 4 + core/os/os2/stat_wasi.odin | 4 + core/path/filepath/path.odin | 186 ++++----------------------- core/path/filepath/path_js.odin | 16 --- core/path/filepath/path_unix.odin | 23 +--- core/path/filepath/path_wasi.odin | 18 +-- core/path/filepath/path_windows.odin | 72 +---------- core/time/timezone/tz_unix.odin | 12 +- 13 files changed, 75 insertions(+), 301 deletions(-) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 1d3dca5c9..2c6412a8f 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -328,9 +328,9 @@ split_path :: proc(path: string) -> (dir, filename: string) { Gets the file name and extension from a path. e.g. - 'path/to/name.tar.gz' -> 'name.tar.gz' - 'path/to/name.txt' -> 'name.txt' - 'path/to/name' -> 'name' + 'path/to/name.tar.gz' -> 'name.tar.gz' + 'path/to/name.txt' -> 'name.txt' + 'path/to/name' -> 'name' Returns "." if the path is an empty string. */ @@ -352,8 +352,8 @@ Only the last dot is considered when splitting the file extension. See `short_stem`. e.g. - 'name.tar.gz' -> 'name.tar' - 'name.txt' -> 'name' + 'name.tar.gz' -> 'name.tar' + 'name.txt' -> 'name' Returns an empty string if there is no stem. e.g: '.gitignore'. Returns an empty string if there's a trailing path separator. @@ -389,8 +389,8 @@ where `long_ext` is the extension returned by `split_filename_all`. The first dot is used to split off the file extension, unlike `stem` which uses the last dot. e.g. - 'name.tar.gz' -> 'name' - 'name.txt' -> 'name' + 'name.tar.gz' -> 'name' + 'name.txt' -> 'name' Returns an empty string if there is no stem. e.g: '.gitignore'. Returns an empty string if there's a trailing path separator. @@ -412,8 +412,8 @@ Only the last dot is considered when splitting the file extension. See `long_ext`. e.g. - 'name.tar.gz' -> '.gz' - 'name.txt' -> '.txt' + 'name.tar.gz' -> '.gz' + 'name.txt' -> '.txt' Returns an empty string if there is no dot. Returns an empty string if there is a trailing path separator. @@ -435,8 +435,8 @@ The long file extension is such that `short_stem(path)` + `long_ext(path)` = `ba The first dot is used to split off the file extension, unlike `ext` which uses the last dot. e.g. - 'name.tar.gz' -> '.tar.gz' - 'name.txt' -> '.txt' + 'name.tar.gz' -> '.tar.gz' + 'name.txt' -> '.txt' Returns an empty string if there is no dot. Returns an empty string if there is a trailing path separator. @@ -616,7 +616,7 @@ Pattern syntax is: '*' matches any sequence of non-/ characters '?' matches any single non-/ character '[' ['^'] { character-range } ']' - character classification (cannot be empty) + character classification (cannot be empty) c matches character c (c != '*', '?', '\\', '[') '\\' c matches character c @@ -728,8 +728,8 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str Returns leading volume name. e.g. - "C:\foo\bar\baz" will return "C:" on Windows. - Everything else will be "". + "C:\foo\bar\baz" will return "C:" on Windows. + Everything else will be "". */ volume_name :: proc(path: string) -> string { when ODIN_OS == .Windows { diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index f1b58c35b..275fe3e18 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -157,6 +157,7 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err } } +@(private) can_use_long_paths: bool @(init) diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 58df45754..0a9ac4e57 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -111,3 +111,7 @@ modification_time_by_path :: proc(path: string) -> (time.Time, Error) { fi, err := stat(path, temp_allocator) return fi.modification_time, err } + +is_reserved_name :: proc(path: string) -> bool { + return _is_reserved_name(path) +} \ No newline at end of file diff --git a/core/os/os2/stat_js.odin b/core/os/os2/stat_js.odin index 439226490..e37864936 100644 --- a/core/os/os2/stat_js.odin +++ b/core/os/os2/stat_js.odin @@ -18,4 +18,8 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath +} + +_is_reserved_name :: proc(path: string) -> bool { + return false } \ No newline at end of file diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 6185252cf..dc5bccb54 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -73,3 +73,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } + +_is_reserved_name :: proc(path: string) -> bool { + return false +} \ No newline at end of file diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 4ed96b389..e401ffe40 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -135,3 +135,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } + +_is_reserved_name :: proc(path: string) -> bool { + return false +} \ No newline at end of file diff --git a/core/os/os2/stat_wasi.odin b/core/os/os2/stat_wasi.odin index bf18d8273..f15479e22 100644 --- a/core/os/os2/stat_wasi.odin +++ b/core/os/os2/stat_wasi.odin @@ -98,3 +98,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } + +_is_reserved_name :: proc(path: string) -> bool { + return false +} \ No newline at end of file diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index efc1707af..db8269cc2 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -16,10 +16,30 @@ is_slash :: proc(c: byte) -> bool { return c == '\\' || c == '/' } +/* + In Windows, returns `true` if `path` is one of the following: + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + + On other platforms, returns `false`. +*/ +is_reserved_name :: os.is_reserved_name + // Splits path immediate following the last separator; separating the path into a directory and file. // If no separator is found, `dir` will be empty and `path` set to `path`. split :: os.split_path + +/* +Join all `elems` with the system's path separator and normalize the result. + +*Allocates Using Provided Allocator* + +For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`. +*/ +join :: os.join_path + /* Returns leading volume name. @@ -27,79 +47,7 @@ split :: os.split_path "C:\foo\bar\baz" will return "C:" on Windows. Everything else will be "". */ -volume_name :: proc(path: string) -> string { - return path[:volume_name_len(path)] -} - -// Returns the length of the volume name in bytes. -volume_name_len :: proc(path: string) -> int { - if ODIN_OS == .Windows { - if len(path) < 2 { - return 0 - } - - if path[1] == ':' { - switch path[0] { - case 'a'..='z', 'A'..='Z': - return 2 - } - } - - /* - See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - Further allowed paths can be of the form of: - - \\server\share or \\server\share\more\path - - \\?\C:\... - - \\.\PhysicalDriveX - */ - // Any remaining kind of path has to start with two slashes. - if !is_separator(path[0]) || !is_separator(path[1]) { - return 0 - } - - // Device path. The volume name is the whole string - if len(path) >= 5 && path[2] == '.' && is_separator(path[3]) { - return len(path) - } - - // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share` - prefix := 2 - - // File namespace. - if len(path) >= 5 && path[2] == '?' && is_separator(path[3]) { - if is_separator(path[4]) { - // `\\?\\` UNC path in file namespace - prefix = 5 - } - - if len(path) >= 6 && path[5] == ':' { - switch path[4] { - case 'a'..='z', 'A'..='Z': - return 6 - case: - return 0 - } - } - } - - // UNC path, minimum version of the volume is `\\h\s` for host, share. - // Can also contain an IP address in the host position. - slash_count := 0 - for i in prefix.. 0 { - slash_count += 1 - - if slash_count == 2 { - return i - } - } - } - - return len(path) - } - return 0 -} +volume_name :: os.volume_name /* Gets the file name and extension from a path. @@ -194,87 +142,7 @@ long_ext :: os.long_ext If the result of the path is an empty string, the returned path with be `"."`. */ -clean :: proc(path: string, allocator := context.allocator) -> (cleaned: string, err: runtime.Allocator_Error) #optional_allocator_error { - context.allocator = allocator - - path := path - original_path := path - vol_len := volume_name_len(path) - path = path[vol_len:] - - if path == "" { - if vol_len > 1 && original_path[1] != ':' { - s, ok := from_slash(original_path) - if !ok { - s = strings.clone(s) or_return - } - return s, nil - } - return strings.concatenate({original_path, "."}) - } - - rooted := is_separator(path[0]) - - n := len(path) - out := &Lazy_Buffer{ - s = path, - vol_and_path = original_path, - vol_len = vol_len, - } - defer lazy_buffer_destroy(out) - - r, dot_dot := 0, 0 - if rooted { - lazy_buffer_append(out, SEPARATOR) or_return - r, dot_dot = 1, 1 - } - - for r < n { - switch { - case is_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_separator(path[r+1])): - r += 1 - case path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_separator(path[r+2])): - r += 2 - switch { - case out.w > dot_dot: - out.w -= 1 - for out.w > dot_dot && !is_separator(lazy_buffer_index(out, out.w)) { - out.w -= 1 - } - case !rooted: - if out.w > 0 { - lazy_buffer_append(out, SEPARATOR) or_return - } - lazy_buffer_append(out, '.') or_return - lazy_buffer_append(out, '.') or_return - dot_dot = out.w - } - case: - if rooted && out.w != 1 || !rooted && out.w != 0 { - lazy_buffer_append(out, SEPARATOR) or_return - } - for ; r < n && !is_separator(path[r]); r += 1 { - lazy_buffer_append(out, path[r]) or_return - } - - } - } - - if out.w == 0 { - lazy_buffer_append(out, '.') or_return - } - - s := lazy_buffer_string(out) or_return - - new_allocation: bool - cleaned, new_allocation = from_slash(s) - if new_allocation { - delete(s) - } - return -} +clean :: os.clean_path // Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character. from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) { @@ -295,7 +163,6 @@ to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: str Relative_Error :: enum { None, - Cannot_Relate, } @@ -308,8 +175,10 @@ Relative_Error :: enum { */ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) { context.allocator = allocator - base_clean := clean(base_path, allocator) - target_clean := clean(target_path, allocator) + base_clean, base_err := clean(base_path, allocator) + if base_err != nil { return "", .Cannot_Relate} + target_clean, target_err := clean(target_path, allocator) + if target_err != nil { return "", .Cannot_Relate} defer delete(base_clean, allocator) defer delete(target_clean, allocator) @@ -390,7 +259,8 @@ dir :: proc(path: string, allocator := context.allocator) -> string { for i >= len(vol) && !is_separator(path[i]) { i -= 1 } - dir := clean(path[len(vol) : i+1]) + dir, dir_err := clean(path[len(vol) : i+1], allocator) + if dir_err != nil { return "" } defer delete(dir) if dir == "." && len(vol) > 2 { return strings.clone(vol) diff --git a/core/path/filepath/path_js.odin b/core/path/filepath/path_js.odin index 3b5ac04f5..c0c85b487 100644 --- a/core/path/filepath/path_js.odin +++ b/core/path/filepath/path_js.odin @@ -1,17 +1,12 @@ package filepath import "base:runtime" - import "core:strings" SEPARATOR :: '/' SEPARATOR_STRING :: `/` LIST_SEPARATOR :: ':' -is_reserved_name :: proc(path: string) -> bool { - return false -} - is_abs :: proc(path: string) -> bool { return strings.has_prefix(path, "/") } @@ -22,15 +17,4 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { } return path, false -} - -join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { - for e, i in elems { - if e != "" { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return - return clean(p, allocator) - } - } - return "", nil } \ No newline at end of file diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 8bf412599..e43264961 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,19 +1,13 @@ #+build linux, darwin, freebsd, openbsd, netbsd, haiku package filepath -import "base:runtime" - -import "core:strings" -import "core:sys/posix" +import "core:strings" +import "core:sys/posix" SEPARATOR :: '/' SEPARATOR_STRING :: `/` LIST_SEPARATOR :: ':' -is_reserved_name :: proc(path: string) -> bool { - return false -} - is_abs :: proc(path: string) -> bool { return strings.has_prefix(path, "/") } @@ -32,15 +26,4 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { path_str := strings.clone(string(path_ptr), allocator) return path_str, true -} - -join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { - for e, i in elems { - if e != "" { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return - return clean(p, allocator) - } - } - return "", nil -} +} \ No newline at end of file diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin index 74cc6ca1e..c0c85b487 100644 --- a/core/path/filepath/path_wasi.odin +++ b/core/path/filepath/path_wasi.odin @@ -1,17 +1,12 @@ package filepath import "base:runtime" - import "core:strings" SEPARATOR :: '/' SEPARATOR_STRING :: `/` LIST_SEPARATOR :: ':' -is_reserved_name :: proc(path: string) -> bool { - return false -} - is_abs :: proc(path: string) -> bool { return strings.has_prefix(path, "/") } @@ -22,15 +17,4 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { } return path, false -} - -join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { - for e, i in elems { - if e != "" { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return - return clean(p, allocator) - } - } - return "", nil -} +} \ No newline at end of file diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 5b81d57a0..00aae8d7f 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -1,6 +1,5 @@ package filepath -import "core:strings" import "base:runtime" import os "core:os/os2" @@ -8,27 +7,8 @@ SEPARATOR :: '\\' SEPARATOR_STRING :: `\` LIST_SEPARATOR :: ';' -@(private) -reserved_names := [?]string{ - "CON", "PRN", "AUX", "NUL", - "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", -} - -is_reserved_name :: proc(path: string) -> bool { - if len(path) == 0 { - return false - } - for reserved in reserved_names { - if strings.equal_fold(path, reserved) { - return true - } - } - return false -} - is_UNC :: proc(path: string) -> bool { - return volume_name_len(path) > 2 + return len(volume_name(path)) > 2 } is_abs :: proc(path: string) -> bool { @@ -43,52 +23,4 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { } p, _ := clean(full_path, allocator) return p, true -} - -join :: proc(elems: []string, allocator := context.allocator) -> (string, runtime.Allocator_Error) #optional_allocator_error { - for e, i in elems { - if e != "" { - return join_non_empty(elems[i:], allocator) - } - } - return "", nil -} - -join_non_empty :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) { - context.allocator = allocator - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) - - if len(elems[0]) == 2 && elems[0][1] == ':' { - i := 1 - for ; i < len(elems); i += 1 { - if elems[i] != "" { - break - } - } - s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return - s = strings.concatenate({elems[0], s}, context.temp_allocator) or_return - s, _ = clean(s) - return - } - - p := strings.join(elems, SEPARATOR_STRING, context.temp_allocator) or_return - p = clean(p) or_return - if !is_UNC(p) { - return p, nil - } - - head := clean(elems[0], context.temp_allocator) or_return - if is_UNC(head) { - return p, nil - } - delete(p) // It is not needed now - - tail := strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator) or_return - tail = clean(tail, context.temp_allocator) or_return - if head[len(head)-1] == SEPARATOR { - return strings.concatenate({head, tail}) - } - - return strings.concatenate({head, SEPARATOR_STRING, tail}) -} +} \ No newline at end of file diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index ac6a4bb50..6bab440d9 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -4,7 +4,6 @@ package timezone import os "core:os/os2" import "core:strings" -import "core:path/filepath" import "core:time/datetime" local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { @@ -36,12 +35,12 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: } // Looking for tz path (ex fmt: "UTC", "Etc/UTC" or "America/Los_Angeles") - path_dir, path_file := filepath.split(path) + path_dir, path_file := os.split_path(path) if path_dir == "" { return } - upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)]) + upper_path_dir, upper_path_chunk := os.split_path(path_dir[:len(path_dir)]) if upper_path_dir == "" { return } @@ -51,8 +50,8 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: if err != nil { return } return region_str, true } else { - region_str, err := filepath.join({upper_path_chunk, path_file}, allocator = allocator) - if err != nil { return } + region_str, region_str_err := os.join_path({upper_path_chunk, path_file}, allocator = allocator) + if region_str_err != nil { return } return region_str, true } } @@ -80,6 +79,7 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r delete(local_name, allocator) return nil, true } + reg_str = local_name } defer if _reg_str == "local" { delete(reg_str, allocator) } @@ -107,4 +107,4 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r } return nil, false -} +} \ No newline at end of file From 2e970db51dc6e9edaefd6e3346807d6974bd155d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 17:19:46 +0100 Subject: [PATCH 35/59] Link some more of filepath to os2 --- core/odin/parser/parse_files.odin | 8 +- core/os/os2/dir_walker.odin | 2 +- core/os/os2/path.odin | 29 ++++- core/path/filepath/path.odin | 143 ++++--------------------- core/path/filepath/path_js.odin | 17 +-- core/path/filepath/path_unix.odin | 25 +---- core/path/filepath/path_wasi.odin | 17 +-- core/path/filepath/path_windows.odin | 17 --- core/path/filepath/walk.odin | 152 +++++++++++++++------------ 9 files changed, 139 insertions(+), 271 deletions(-) diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 5e7bee923..93c282d35 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -11,8 +11,8 @@ import "core:strings" collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { NO_POS :: tokenizer.Pos{} - pkg_path, pkg_path_ok := filepath.abs(path) - if !pkg_path_ok { + pkg_path, pkg_path_err := os.get_absolute_path(path, context.allocator) + if pkg_path_err != nil { return } @@ -28,8 +28,8 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { pkg.fullpath = pkg_path for match in matches { - fullpath, ok := filepath.abs(match) - if !ok { + fullpath, fullpath_err := os.get_absolute_path(match, context.allocator) + if fullpath_err != nil { return } diff --git a/core/os/os2/dir_walker.odin b/core/os/os2/dir_walker.odin index 0af751f31..ba5342cf8 100644 --- a/core/os/os2/dir_walker.odin +++ b/core/os/os2/dir_walker.odin @@ -227,4 +227,4 @@ walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) { } return info, iter_ok -} +} \ No newline at end of file diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 2c6412a8f..ac18b7562 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -22,9 +22,32 @@ is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) } -@(private) -is_slash :: proc(c: byte) -> bool { - return c == '\\' || c == '/' +/* +Returns the result of replacing each path separator character in the path +with the `new_sep` rune. + +*Allocates Using Provided Allocator* +*/ +replace_path_separators :: proc(path: string, new_sep: rune, allocator: runtime.Allocator) -> (new_path: string, err: Error) { + buf := make([]u8, len(path), allocator) or_return + + i: int + for r in path { + replacement := r + if r == '/' || r == '\\' { + replacement = new_sep + } + + if replacement <= rune(0x7F) { + buf[i] = u8(replacement) + i += 1 + } else { + b, w := utf8.encode_rune(r) + copy(buf[i:], b[:w]) + i += w + } + } + return string(buf), nil } mkdir :: make_directory diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index db8269cc2..2f2f7996c 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -2,7 +2,6 @@ // To process paths such as URLs that depend on forward slashes regardless of the OS, use the slashpath package. package filepath -import "base:runtime" import os "core:os/os2" import "core:strings" @@ -11,11 +10,6 @@ SEPARATOR_CHARS :: `/\` // is_separator checks whether the byte is a valid separator character is_separator :: os.is_path_separator -@(private) -is_slash :: proc(c: byte) -> bool { - return c == '\\' || c == '/' -} - /* In Windows, returns `true` if `path` is one of the following: "CON", "PRN", "AUX", "NUL", @@ -144,22 +138,25 @@ long_ext :: os.long_ext */ clean :: os.clean_path -// Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character. -from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) { - if SEPARATOR == '/' { - return path, false - } - return strings.replace_all(path, "/", SEPARATOR_STRING, allocator) -} +/* +Returns the result of replacing each path separator character in the path +with the specific character `new_sep`. -// Returns the result of replacing each OS specific separator with a forward slash `/` character. -to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) { - if SEPARATOR == '/' { - return path, false - } - return strings.replace_all(path, SEPARATOR_STRING, "/", allocator) -} +*Allocates Using Provided Allocator* +*/ +replace_path_separators := os.replace_path_separators +/* +Return true if `path` is an absolute path as opposed to a relative one. +*/ +is_abs :: os.is_absolute_path + +/* +Get the absolute path to `path` with respect to the process's current directory. + +*Allocates Using Provided Allocator* +*/ +abs :: os.get_absolute_path Relative_Error :: enum { None, @@ -275,108 +272,4 @@ dir :: proc(path: string, allocator := context.allocator) -> string { // An empty string returns nil. A non-empty string with no separators returns a 1-element array. // Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`. // Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}. -split_list :: proc(path: string, allocator := context.allocator) -> (list: []string, err: runtime.Allocator_Error) #optional_allocator_error { - if path == "" { - return nil, nil - } - - start: int - quote: bool - - start, quote = 0, false - count := 0 - - for i := 0; i < len(path); i += 1 { - c := path[i] - switch { - case c == '"': - quote = !quote - case c == LIST_SEPARATOR && !quote: - count += 1 - } - } - - start, quote = 0, false - list = make([]string, count + 1, allocator) or_return - index := 0 - for i := 0; i < len(path); i += 1 { - c := path[i] - switch { - case c == '"': - quote = !quote - case c == LIST_SEPARATOR && !quote: - list[index] = path[start:i] - index += 1 - start = i + 1 - } - } - assert(index == count) - list[index] = path[start:] - - for s0, i in list { - s, new := strings.replace_all(s0, `"`, ``, allocator) - if !new { - s = strings.clone(s, allocator) or_return - } - list[i] = s - } - - return list, nil -} - - - - -/* - Lazy_Buffer is a lazily made path buffer - When it does allocate, it uses the context.allocator - */ -@(private) -Lazy_Buffer :: struct { - s: string, - b: []byte, - w: int, // write index - vol_and_path: string, - vol_len: int, -} - -@(private) -lazy_buffer_index :: proc(lb: ^Lazy_Buffer, i: int) -> byte { - if lb.b != nil { - return lb.b[i] - } - return lb.s[i] -} -@(private) -lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) -> (err: runtime.Allocator_Error) { - if lb.b == nil { - if lb.w < len(lb.s) && lb.s[lb.w] == c { - lb.w += 1 - return - } - lb.b = make([]byte, len(lb.s)) or_return - copy(lb.b, lb.s[:lb.w]) - } - lb.b[lb.w] = c - lb.w += 1 - return -} -@(private) -lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> (s: string, err: runtime.Allocator_Error) { - if lb.b == nil { - return strings.clone(lb.vol_and_path[:lb.vol_len+lb.w]) - } - - x := lb.vol_and_path[:lb.vol_len] - y := string(lb.b[:lb.w]) - z := make([]byte, len(x)+len(y)) or_return - copy(z, x) - copy(z[len(x):], y) - return string(z), nil -} -@(private) -lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) -> runtime.Allocator_Error { - err := delete(lb.b) - lb^ = {} - return err -} \ No newline at end of file +split_list :: os.split_path_list \ No newline at end of file diff --git a/core/path/filepath/path_js.odin b/core/path/filepath/path_js.odin index c0c85b487..e2657cb3e 100644 --- a/core/path/filepath/path_js.odin +++ b/core/path/filepath/path_js.odin @@ -1,20 +1,5 @@ package filepath -import "base:runtime" -import "core:strings" - SEPARATOR :: '/' SEPARATOR_STRING :: `/` -LIST_SEPARATOR :: ':' - -is_abs :: proc(path: string) -> bool { - return strings.has_prefix(path, "/") -} - -abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { - if is_abs(path) { - return strings.clone(string(path), allocator), true - } - - return path, false -} \ No newline at end of file +LIST_SEPARATOR :: ':' \ No newline at end of file diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index e43264961..2e1b1419e 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,29 +1,6 @@ #+build linux, darwin, freebsd, openbsd, netbsd, haiku package filepath -import "core:strings" -import "core:sys/posix" - SEPARATOR :: '/' SEPARATOR_STRING :: `/` -LIST_SEPARATOR :: ':' - -is_abs :: proc(path: string) -> bool { - return strings.has_prefix(path, "/") -} - -abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { - rel := path - if rel == "" { - rel = "." - } - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - path_ptr := posix.realpath(rel_cstr, nil) - if path_ptr == nil { - return "", posix.errno() == nil - } - defer posix.free(path_ptr) - - path_str := strings.clone(string(path_ptr), allocator) - return path_str, true -} \ No newline at end of file +LIST_SEPARATOR :: ':' \ No newline at end of file diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin index c0c85b487..e2657cb3e 100644 --- a/core/path/filepath/path_wasi.odin +++ b/core/path/filepath/path_wasi.odin @@ -1,20 +1,5 @@ package filepath -import "base:runtime" -import "core:strings" - SEPARATOR :: '/' SEPARATOR_STRING :: `/` -LIST_SEPARATOR :: ':' - -is_abs :: proc(path: string) -> bool { - return strings.has_prefix(path, "/") -} - -abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { - if is_abs(path) { - return strings.clone(string(path), allocator), true - } - - return path, false -} \ No newline at end of file +LIST_SEPARATOR :: ':' \ No newline at end of file diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 00aae8d7f..9b8e92bbd 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -1,26 +1,9 @@ package filepath -import "base:runtime" -import os "core:os/os2" - SEPARATOR :: '\\' SEPARATOR_STRING :: `\` LIST_SEPARATOR :: ';' is_UNC :: proc(path: string) -> bool { return len(volume_name(path)) > 2 -} - -is_abs :: proc(path: string) -> bool { - return os.is_absolute_path(path) -} - -abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) - full_path, err := os.get_absolute_path(path, context.temp_allocator) - if err != nil { - return "", false - } - p, _ := clean(full_path, allocator) - return p, true } \ No newline at end of file diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 845ba06a0..00b063bc7 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -3,81 +3,103 @@ package filepath import os "core:os/os2" -import "core:slice" -// Walk_Proc is the type of the procedure called for each file or directory visited by 'walk' -// The 'path' parameter contains the parameter to walk as a prefix (this is the same as info.fullpath except on 'root') -// The 'info' parameter is the os.File_Info for the named path -// -// If there was a problem walking to the file or directory named by path, the incoming error will describe the problem -// and the procedure can decide how to handle that error (and walk will not descend into that directory) -// In the case of an error, the info argument will be 0 -// If an error is returned, processing stops -// The sole exception is if 'skip_dir' is returned as true: -// when 'skip_dir' is invoked on a directory. 'walk' skips directory contents -// when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory -Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool) +Walker :: os.Walker -// walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root' -// All errors that happen visiting files and directories are filtered by walk_proc -// The files are walked in lexical order to make the output deterministic -// NOTE: Walking large directories can be inefficient due to the lexical sort -// NOTE: walk does not follow symbolic links -// NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done -walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error { - info, err := os.lstat(root, context.temp_allocator) - defer os.file_info_delete(info, context.temp_allocator) +/* +Initializes a walker, either using a path or a file pointer to a directory the walker will start at. - skip_dir: bool - if err != nil { - err, skip_dir = walk_proc(info, err, user_data) - } else { - err, skip_dir = _walk(info, walk_proc, user_data) - } - return nil if skip_dir else err -} +You are allowed to repeatedly call this to reuse it for later walks. +For an example on how to use the walker, see `walker_walk`. +*/ +walker_init :: os.walker_init -@(private) -_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) { - if info.type != .Directory { - if info.fullpath == "" && info.name == "" { - // ignore empty things - return - } - return walk_proc(info, nil, user_data) - } +/* +Creates a walker, either using a path or a file pointer to a directory the walker will start at. - fis: []os.File_Info - err1: os.Error - fis, err = read_dir(info.fullpath, context.temp_allocator) - defer os.file_info_slice_delete(fis, context.temp_allocator) +For an example on how to use the walker, see `walker_walk`. +*/ +walker_create :: os.walker_create - err1, skip_dir = walk_proc(info, err, user_data) - if err != nil || err1 != nil || skip_dir { - err = err1 - return - } +/* +Returns the last error that occurred during the walker's operations. - for fi in fis { - err, skip_dir = _walk(fi, walk_proc, user_data) - if err != nil || skip_dir { - if fi.type != .Directory || !skip_dir { - return +Can be called while iterating, or only at the end to check if anything failed. +*/ +walker_error :: os.walker_error + +walker_destroy :: os.walker_destroy + +// Marks the current directory to be skipped (not entered into). +walker_skip_dir :: os.walker_skip_dir + +/* +Returns the next file info in the iterator, files are iterated in breadth-first order. + +If an error occurred opening a directory, you may get zero'd info struct and +`walker_error` will return the error. + +Example: + package main + + import "core:fmt" + import "core:strings" + import os "core:os/os2" + + main :: proc() { + w := os.walker_create("core") + defer os.walker_destroy(&w) + + for info in os.walker_walk(&w) { + // Optionally break on the first error: + // _ = walker_error(&w) or_break + + // Or, handle error as we go: + if path, err := os.walker_error(&w); err != nil { + fmt.eprintfln("failed walking %s: %s", path, err) + continue } + + // Or, do not handle errors during iteration, and just check the error at the end. + + + + // Skip a directory: + if strings.has_suffix(info.fullpath, ".git") { + os.walker_skip_dir(&w) + continue + } + + fmt.printfln("%#v", info) + } + + // Handle error if one happened during iteration at the end: + if path, err := os.walker_error(&w); err != nil { + fmt.eprintfln("failed walking %s: %v", path, err) } } +*/ +walker_walk :: os.walker_walk - return -} +/* + Reads the file `f` (assuming it is a directory) and returns the unsorted directory entries. + This returns up to `n` entries OR all of them if `n <= 0`. +*/ +read_directory :: os.read_directory -@(private) -read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) { - f := os.open(dir_name, os.O_RDONLY) or_return - defer os.close(f) - fis = os.read_dir(f, -1, allocator) or_return - slice.sort_by(fis, proc(a, b: os.File_Info) -> bool { - return a.name < b.name - }) - return -} +/* + Reads the file `f` (assuming it is a directory) and returns all of the unsorted directory entries. +*/ +read_all_directory :: os.read_all_directory + +/* + Reads the named directory by path (assuming it is a directory) and returns the unsorted directory entries. + This returns up to `n` entries OR all of them if `n <= 0`. +*/ +read_directory_by_path :: os.read_directory_by_path + +/* + Reads the named directory by path (assuming it is a directory) and returns all of the unsorted directory entries. +*/ +read_all_directory_by_path :: os.read_all_directory_by_path \ No newline at end of file From 062d7ae3858f1e66ba1cc36574c2c5c2d337150a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 17:56:26 +0100 Subject: [PATCH 36/59] Port vendor:OpenGL and vendor:fontstash --- vendor/OpenGL/helpers.odin | 38 +++++++++++++++++++----------- vendor/fontstash/fontstash_os.odin | 11 ++++----- vendor/libc-shim/stdlib.odin | 3 +-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/vendor/OpenGL/helpers.odin b/vendor/OpenGL/helpers.odin index 84e3eae81..0610eb3ac 100644 --- a/vendor/OpenGL/helpers.odin +++ b/vendor/OpenGL/helpers.odin @@ -3,10 +3,11 @@ package vendor_gl // Helper for loading shaders into a program -import "core:os" -import "core:fmt" -import "core:strings" -import "base:runtime" +import os "core:os/os2" +import "core:fmt" +import "core:strings" +import "core:time" +import "base:runtime" _ :: fmt _ :: runtime @@ -150,7 +151,10 @@ create_and_link_program :: proc(shader_ids: []u32, binary_retrievable := false) } load_compute_file :: proc(filename: string, binary_retrievable := false) -> (program_id: u32, ok: bool) { - cs_data := os.read_entire_file(filename) or_return + cs_data, cs_data_err := os.read_entire_file(filename, context.allocator) + if cs_data_err != nil { + return 0, false + } defer delete(cs_data) // Create the shaders @@ -165,10 +169,16 @@ load_compute_source :: proc(cs_data: string, binary_retrievable := false) -> (pr } load_shaders_file :: proc(vs_filename, fs_filename: string, binary_retrievable := false) -> (program_id: u32, ok: bool) { - vs_data := os.read_entire_file(vs_filename) or_return + vs_data, vs_data_err := os.read_entire_file(vs_filename, context.allocator) + if vs_data_err != nil { + return 0, false + } defer delete(vs_data) - fs_data := os.read_entire_file(fs_filename) or_return + fs_data, fs_data_err := os.read_entire_file(fs_filename, context.allocator) + if fs_data_err != nil { + return 0, false + } defer delete(fs_data) return load_shaders_source(string(vs_data), string(fs_data), binary_retrievable) @@ -192,14 +202,14 @@ when ODIN_OS == .Windows { update_shader_if_changed :: proc( vertex_name, fragment_name: string, program: u32, - last_vertex_time, last_fragment_time: os.File_Time, + last_vertex_time, last_fragment_time: time.Time, ) -> ( old_program: u32, - current_vertex_time, current_fragment_time: os.File_Time, + current_vertex_time, current_fragment_time: time.Time, updated: bool, ) { - current_vertex_time, _ = os.last_write_time_by_name(vertex_name) - current_fragment_time, _ = os.last_write_time_by_name(fragment_name) + current_vertex_time, _ = os.modification_time_by_path(vertex_name) + current_fragment_time, _ = os.modification_time_by_path(fragment_name) old_program = program if current_vertex_time != last_vertex_time || current_fragment_time != last_fragment_time { @@ -220,13 +230,13 @@ when ODIN_OS == .Windows { update_shader_if_changed_compute :: proc( compute_name: string, program: u32, - last_compute_time: os.File_Time, + last_compute_time: time.Time, ) -> ( old_program: u32, - current_compute_time: os.File_Time, + current_compute_time: time.Time, updated: bool, ) { - current_compute_time, _ = os.last_write_time_by_name(compute_name) + current_compute_time, _ = os.modification_time_by_path(compute_name) old_program = program if current_compute_time != last_compute_time { diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin index e510a4834..8c259412d 100644 --- a/vendor/fontstash/fontstash_os.odin +++ b/vendor/fontstash/fontstash_os.odin @@ -1,8 +1,8 @@ #+build !js package fontstash -import "core:log" -import "core:os" +import "core:log" +import os "core:os/os2" // 'fontIndex' controls which font you want to load within a multi-font format such // as TTC. Leave it as zero if you are loading a single-font format such as TTF. @@ -12,12 +12,11 @@ AddFontPath :: proc( path: string, fontIndex: int = 0, ) -> int { - data, ok := os.read_entire_file(path) + data, data_err := os.read_entire_file(path, context.allocator) - if !ok { + if data_err != nil { log.panicf("FONT: failed to read font at %s", path) } return AddFontMem(ctx, name, data, true, fontIndex) -} - +} \ No newline at end of file diff --git a/vendor/libc-shim/stdlib.odin b/vendor/libc-shim/stdlib.odin index cffc66ed2..5dd4c53c1 100644 --- a/vendor/libc-shim/stdlib.odin +++ b/vendor/libc-shim/stdlib.odin @@ -5,7 +5,6 @@ import "base:intrinsics" import "base:runtime" import "core:c" -import "core:os" import "core:slice" import "core:sort" import "core:strconv" @@ -166,7 +165,7 @@ atexit :: proc "c" (function: proc "c" ()) -> i32 { @(require, linkage="strong", link_name="exit") exit :: proc "c" (exit_code: c.int) -> ! { finish_atexit() - os.exit(int(exit_code)) + runtime.exit(int(exit_code)) } @(private, fini) From 2efefaac1add693fb49c11513ab963d5c30e2c30 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 18:21:34 +0100 Subject: [PATCH 37/59] Update example --- core/text/table/doc.odin | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 4b8d76893..29b60dbe2 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -189,12 +189,13 @@ Example: import "core:fmt" import "core:io" - import "core:os" + import os "core:os/os2" import "core:text/table" scripts :: proc(w: io.Writer) { t: table.Table table.init(&t) + defer table.destroy(&t) table.caption(&t, "Tést Suite") table.padding(&t, 1, 3) table.header_of_aligned_values(&t, {{.Left, "Script"}, {.Center, "Sample"}}) @@ -224,9 +225,7 @@ Example: } main :: proc() { - stdout := os.stream_from_handle(os.stdout) - - scripts(stdout) + scripts(os.to_stream(os.stdout)) } Output: @@ -266,14 +265,15 @@ corners and dividers. Example: package main - import "core:fmt" - import "core:io" - import "core:os" - import "core:text/table" + import "core:fmt" + import "core:io" + import os "core:os/os2" + import "core:text/table" box_drawing :: proc(w: io.Writer) { t: table.Table table.init(&t) + defer table.destroy(&t) table.caption(&t, "Box Drawing Example") table.padding(&t, 2, 2) table.header_of_aligned_values(&t, {{.Left, "Operating System"}, {.Center, "Year Introduced"}}) @@ -299,9 +299,7 @@ Example: } main :: proc() { - stdout := os.stream_from_handle(os.stdout) - - box_drawing(stdout) + box_drawing(os.to_stream(os.stdout)) } While the decorations support multi-codepoint Unicode graphemes, do note that From 13228c14e86d357ceac616335526d805e3965940 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:04:44 +0100 Subject: [PATCH 38/59] More conflicts during rebase --- core/flags/errors.odin | 6 +-- core/flags/example/example.odin | 8 +-- core/flags/internal_rtti.odin | 54 ++++++++++--------- core/flags/internal_validation.odin | 26 ++++----- core/flags/util.odin | 12 ++--- core/time/timezone/tz_unix.odin | 7 ++- tests/core/flags/test_core_flags.odin | 10 ++-- .../path/filepath/test_core_filepath.odin | 12 ++--- 8 files changed, 70 insertions(+), 65 deletions(-) diff --git a/core/flags/errors.odin b/core/flags/errors.odin index e9b2e18c8..d0caa1427 100644 --- a/core/flags/errors.odin +++ b/core/flags/errors.odin @@ -2,7 +2,7 @@ package flags import "base:runtime" import "core:net" -import "core:os" +import os "core:os/os2" Parse_Error_Reason :: enum { None, @@ -37,8 +37,8 @@ Unified_Parse_Error_Reason :: union #shared_nil { Open_File_Error :: struct { filename: string, errno: os.Error, - mode: int, - perms: int, + flags: os.File_Flags, + perms: os.Permissions, } // Raised during parsing. diff --git a/core/flags/example/example.odin b/core/flags/example/example.odin index a3af44790..6e74c7dcc 100644 --- a/core/flags/example/example.odin +++ b/core/flags/example/example.odin @@ -4,7 +4,7 @@ import "base:runtime" import "core:flags" import "core:fmt" import "core:net" -import "core:os" +import os "core:os/os2" import "core:time/datetime" @@ -76,8 +76,8 @@ Distinct_Int :: distinct int main :: proc() { Options :: struct { - file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`, - output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`, + file: ^os.File `args:"pos=0,required,file=r" usage:"Input file."`, + output: ^os.File `args:"pos=1,file=cw" usage:"Output file."`, hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`, schedule: datetime.DateTime `usage:"Launch tasks at this time."`, @@ -126,7 +126,7 @@ main :: proc() { fmt.printfln("%#v", opt) - if opt.output != 0 { + if opt.output != nil { os.write_string(opt.output, "Hellope!\n") } } diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index b3880afa0..07481a89b 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -6,7 +6,7 @@ import "base:runtime" import "core:fmt" import "core:mem" import "core:net" -import "core:os" +@(require) import os "core:os/os2" import "core:reflect" import "core:strconv" import "core:strings" @@ -209,7 +209,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) { // Core types currently supported: // - // - os.Handle + // - ^os.File // - time.Time // - datetime.DateTime // - net.Host_Or_Endpoint @@ -217,47 +217,44 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`." out_error^ = nil - - if data_type == os.Handle { + if data_type == ^os.File { // NOTE: `os` is hopefully available everywhere, even if it might panic on some calls. - wants_read := false - wants_write := false - mode: int + flags: os.File_Flags if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok { for i in 0..= {.Read, .Write} { + octal_perms |= 0o200 + } else if .Write in flags { + octal_perms |= 0o200 } else { - mode |= os.O_RDONLY + flags |= {.Read} } if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok { if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok { - perms = int(value) + octal_perms = int(value) } } - handle, errno := os.open(str, mode, perms) - if errno != nil { + perms := os.perm(octal_perms) + + f, error := os.open(str, flags, perms) + if error != nil { // NOTE(Feoramund): os.Error is system-dependent, and there's // currently no good way to translate them all into strings. // @@ -267,14 +264,14 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: // it up. out_error^ = Open_File_Error { str, - errno, - mode, + error, + flags, perms, } return } - (^os.Handle)(ptr)^ = handle + (^^os.File)(ptr)^ = f return } @@ -475,6 +472,11 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt } case: + if type_info.id == ^os.File { + parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error) + return + } + if !parse_and_set_pointer_by_base_type(ptr, str, type_info) { return Parse_Error { // The caller will add more details. diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index cd903c3e5..dc19f3084 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -1,14 +1,14 @@ #+private package flags -@require import "base:runtime" -@require import "core:container/bit_array" -@require import "core:fmt" -@require import "core:mem" -@require import "core:os" -@require import "core:reflect" -@require import "core:strconv" -@require import "core:strings" +@require import "base:runtime" +@require import "core:container/bit_array" +@require import "core:fmt" +@require import "core:mem" +@require import os "core:os/os2" +@require import "core:reflect" +@require import "core:strconv" +@require import "core:strings" // This proc is used to assert that `T` meets the expectations of the library. @(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT) @@ -138,20 +138,20 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_ allowed_to_define_file_perms: bool = --- #partial switch specific_type_info in field.type.variant { case runtime.Type_Info_Map: - allowed_to_define_file_perms = specific_type_info.value.id == os.Handle + allowed_to_define_file_perms = specific_type_info.value.id == ^os.File case runtime.Type_Info_Dynamic_Array: - allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle + allowed_to_define_file_perms = specific_type_info.elem.id == ^os.File case: - allowed_to_define_file_perms = field.type.id == os.Handle + allowed_to_define_file_perms = field.type.id == ^os.File } if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file { - fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.", + fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `^os.File` type.", model_type, field.name, SUBTAG_FILE, loc = loc) } if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms { - fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.", + fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `^os.File` type.", model_type, field.name, SUBTAG_PERMS, loc = loc) } diff --git a/core/flags/util.odin b/core/flags/util.odin index ce7e2e36c..20e40cab5 100644 --- a/core/flags/util.odin +++ b/core/flags/util.odin @@ -1,7 +1,7 @@ package flags import "core:fmt" -@require import "core:os" +@require import os "core:os/os2" @require import "core:path/filepath" import "core:strings" @@ -38,7 +38,7 @@ parse_or_exit :: proc( error := parse(model, args, style, true, true, allocator, loc) if error != nil { - stderr := os.stream_from_handle(os.stderr) + stderr := os.to_stream(os.stderr) if len(args) == 0 { // No arguments entered, and there was an error; show the usage, @@ -65,18 +65,18 @@ Inputs: */ @(optimization_mode="favor_size") print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) { - stderr := os.stream_from_handle(os.stderr) - stdout := os.stream_from_handle(os.stdout) + stderr := os.to_stream(os.stderr) + stdout := os.to_stream(os.stdout) switch specific_error in error { case Parse_Error: fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message) case Open_File_Error: - fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s", + fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o and flags %v: %s", specific_error, specific_error.errno, specific_error.perms, - specific_error.mode, + specific_error.flags, specific_error.filename) case Validation_Error: fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message) diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index 6bab440d9..32590f177 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -5,6 +5,7 @@ package timezone import os "core:os/os2" import "core:strings" import "core:time/datetime" +import "core:path/filepath" local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { local_str, ok := os.lookup_env("TZ", allocator) @@ -88,7 +89,8 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r defer if tzdir_ok { delete(tzdir_str, allocator) } if tzdir_ok { - region_path := filepath.join({tzdir_str, reg_str}, allocator) + region_path, err := filepath.join({tzdir_str, reg_str}, allocator) + if err != nil { return nil, false } defer delete(region_path, allocator) if tz_reg, ok := load_tzif_file(region_path, reg_str, allocator); ok { @@ -98,7 +100,8 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r db_paths := []string{"/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo"} for db_path in db_paths { - region_path := filepath.join({db_path, reg_str}, allocator) + region_path, err := filepath.join({db_path, reg_str}, allocator) + if err != nil { return nil, false} defer delete(region_path, allocator) if tz_reg, ok := load_tzif_file(region_path, reg_str, allocator); ok { diff --git a/tests/core/flags/test_core_flags.odin b/tests/core/flags/test_core_flags.odin index 0cfcf8e75..1aee7f69c 100644 --- a/tests/core/flags/test_core_flags.odin +++ b/tests/core/flags/test_core_flags.odin @@ -7,7 +7,7 @@ import "core:fmt" @require import "core:log" import "core:math" @require import "core:net" -import "core:os" +import os "core:os/os2" import "core:strings" import "core:testing" import "core:time/datetime" @@ -1249,7 +1249,7 @@ test_os_handle :: proc(t: ^testing.T) { test_data := "Hellope!" W :: struct { - outf: os.Handle `args:"file=cw"`, + outf: ^os.File `args:"file=cw"`, } w: W @@ -1263,7 +1263,7 @@ test_os_handle :: proc(t: ^testing.T) { os.write_string(w.outf, test_data) R :: struct { - inf: os.Handle `args:"file=r"`, + inf: ^os.File `args:"file=r"`, } r: R @@ -1274,8 +1274,8 @@ test_os_handle :: proc(t: ^testing.T) { return } defer os.close(r.inf) - data, read_ok := os.read_entire_file_from_handle(r.inf, context.temp_allocator) - testing.expect_value(t, read_ok, true) + data, read_err := os.read_entire_file(r.inf, context.temp_allocator) + testing.expect_value(t, read_err, nil) file_contents_equal := 0 == bytes.compare(transmute([]u8)test_data, data) testing.expectf(t, file_contents_equal, "expected file contents to be the same, got %v", data) } diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin index f0137f69b..a0de7e831 100644 --- a/tests/core/path/filepath/test_core_filepath.odin +++ b/tests/core/path/filepath/test_core_filepath.odin @@ -33,7 +33,7 @@ test_split_list_windows :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) - r := filepath.split_list(d.v) + r, _ := filepath.split_list(d.v, context.allocator) defer delete_split(r) testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e))) if len(r) == len(d.e) { @@ -45,13 +45,13 @@ test_split_list_windows :: proc(t: ^testing.T) { { v := "" - r := filepath.split_list(v) + r, _ := filepath.split_list(v, context.allocator) defer delete_split(r) testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) } { v := "a" - r := filepath.split_list(v) + r, _ := filepath.split_list(v, context.allocator) defer delete_split(r) testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) if len(r) == 1 { @@ -77,7 +77,7 @@ test_split_list_unix :: proc(t: ^testing.T) { } for d in data { - r := filepath.split_list(d.v) + r, _ := filepath.split_list(d.v, context.allocator) defer delete_split(r) testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e)) if len(r) == len(d.e) { @@ -89,12 +89,12 @@ test_split_list_unix :: proc(t: ^testing.T) { { v := "" - r := filepath.split_list(v) + r, _ := filepath.split_list(v, context.allocator) testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r) } { v := "a" - r := filepath.split_list(v) + r, _ := filepath.split_list(v, context.allocator) defer delete_split(r) testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r)) if len(r) == 1 { From 5644db99f08ea9a4b59be64b7cc6c96cda870ebc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 11:30:57 +0100 Subject: [PATCH 39/59] require --- vendor/OpenGL/helpers.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/OpenGL/helpers.odin b/vendor/OpenGL/helpers.odin index 0610eb3ac..bce60ee80 100644 --- a/vendor/OpenGL/helpers.odin +++ b/vendor/OpenGL/helpers.odin @@ -6,7 +6,7 @@ package vendor_gl import os "core:os/os2" import "core:fmt" import "core:strings" -import "core:time" +@(require) import "core:time" import "base:runtime" _ :: fmt _ :: runtime From 2c39af1581241e3e7c61d4d1f84aaecaceb712b8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 11:47:27 +0100 Subject: [PATCH 40/59] Fix --- tests/core/normal.odin | 1 - tests/core/os/os2/path.odin | 198 ++++++++++++++++++++++++++++-------- 2 files changed, 156 insertions(+), 43 deletions(-) diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 696510ac1..6b31b9d56 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -38,7 +38,6 @@ download_assets :: proc "contextless" () { @(require) import "odin" @(require) import "os" @(require) import "os/os2" -@(require) import "path/filepath" @(require) import "reflect" @(require) import "runtime" @(require) import "slice" diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin index a256b3e22..868023c86 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/os2/path.odin @@ -336,48 +336,6 @@ test_join_filename :: proc(t: ^testing.T) { } } -@(test) -test_split_path_list :: proc(t: ^testing.T) { - Test_Case :: struct { - path_list: string, - expected: []string, - } - - when ODIN_OS != .Windows { - test_cases := [?]Test_Case { - {``, {}}, - {`/bin:`, {`/bin`, ``}}, - {`/usr/local/bin`, {`/usr/local/bin`}}, - {`/usr/local/bin:/usr/bin`, {`/usr/local/bin`, `/usr/bin`}}, - {`"/extra bin":/bin`, {`/extra bin`, `/bin`}}, - {`"/extra:bin":/bin`, {`/extra:bin`, `/bin`}}, - } - } else { - test_cases := [?]Test_Case { - {``, {}}, - {`C:\bin;`, {`C:\bin`, ``}}, - {`C:\usr\local\bin`, {`C:\usr\local\bin`}}, - {`C:\usr\local\bin;C:\usr\bin`, {`C:\usr\local\bin`, `C:\usr\bin`}}, - {`"C:\extra bin";C:\bin`, {`C:\extra bin`, `C:\bin`}}, - {`"C:\extra;bin";C:\bin`, {`C:\extra;bin`, `C:\bin`}}, - } - } - - for tc in test_cases { - result, err := os.split_path_list(tc.path_list, context.temp_allocator) - if testing.expectf(t, len(result) == len(tc.expected), "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err) { - ok := true - for entry, i in result { - if entry != tc.expected[i] { - ok = false - break - } - } - testing.expectf(t, ok, "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err) - } - } -} - Glob_Test :: struct { pattern: string, matches: []string, @@ -445,4 +403,160 @@ test_glob :: proc(t: ^testing.T) { testing.expect_value(t, err, glob.err) compare_matches(t, glob.pattern, globbed, glob.matches) } +} + + +// TODO: merge this and `test_split_list` +@(test) +test_split_path_list :: proc(t: ^testing.T) { + Test_Case :: struct { + path_list: string, + expected: []string, + } + + when ODIN_OS != .Windows { + test_cases := [?]Test_Case { + {``, {}}, + {`/bin:`, {`/bin`, ``}}, + {`/usr/local/bin`, {`/usr/local/bin`}}, + {`/usr/local/bin:/usr/bin`, {`/usr/local/bin`, `/usr/bin`}}, + {`"/extra bin":/bin`, {`/extra bin`, `/bin`}}, + {`"/extra:bin":/bin`, {`/extra:bin`, `/bin`}}, + } + } else { + test_cases := [?]Test_Case { + {``, {}}, + {`C:\bin;`, {`C:\bin`, ``}}, + {`C:\usr\local\bin`, {`C:\usr\local\bin`}}, + {`C:\usr\local\bin;C:\usr\bin`, {`C:\usr\local\bin`, `C:\usr\bin`}}, + {`"C:\extra bin";C:\bin`, {`C:\extra bin`, `C:\bin`}}, + {`"C:\extra;bin";C:\bin`, {`C:\extra;bin`, `C:\bin`}}, + } + } + + for tc in test_cases { + result, err := os.split_path_list(tc.path_list, context.temp_allocator) + if testing.expectf(t, len(result) == len(tc.expected), "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err) { + ok := true + for entry, i in result { + if entry != tc.expected[i] { + ok = false + break + } + } + testing.expectf(t, ok, "expected split_path_list(%q) -> %v; got %v, %v", tc.path_list, tc.expected, result, err) + } + } +} + +@(test) +test_split_list :: proc(t: ^testing.T) { + when ODIN_OS == .Windows { + test_split_list_windows(t) + } else { + test_split_list_unix(t) + } +} + +test_split_list_windows :: proc(t: ^testing.T) { + Datum :: struct { + i: int, + v: string, + e: [3]string, + } + @static data := []Datum{ + { 0, "C:\\Odin;C:\\Visual Studio;\"C:\\Some Other\"", + [3]string{"C:\\Odin", "C:\\Visual Studio", "C:\\Some Other"} }, // Issue #1537 + { 1, "a;;b", [3]string{"a", "", "b"} }, + { 2, "a;b;", [3]string{"a", "b", ""} }, + { 3, ";a;b", [3]string{"", "a", "b"} }, + { 4, ";;", [3]string{"", "", ""} }, + { 5, "\"a;b\"c;d;\"f\"", [3]string{"a;bc", "d", "f"} }, + { 6, "\"a;b;c\";d\";e\";f", [3]string{"a;b;c", "d;e", "f"} }, + } + + for d, i in data { + assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + r, err := os.split_path_list(d.v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + defer delete_split(r) + testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e))) + if len(r) == len(d.e) { + for _, j in r { + testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", i, #procedure, d.v, r[j], j, d.e[j])) + } + } + } + + { + v := "" + r, err := os.split_path_list(v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + defer delete_split(r) + testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + } + { + v := "a" + r, err := os.split_path_list(v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + defer delete_split(r) + testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + if len(r) == 1 { + testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + } + } +} + +test_split_list_unix :: proc(t: ^testing.T) { + Datum :: struct { + v: string, + e: [3]string, + } + @static data := []Datum{ + { "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", + [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537 + { "a::b", [3]string{"a", "", "b"} }, + { "a:b:", [3]string{"a", "b", ""} }, + { ":a:b", [3]string{"", "a", "b"} }, + { "::", [3]string{"", "", ""} }, + { "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, + { "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, + } + + for d in data { + r, err := os.split_path_list(d.v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + defer delete_split(r) + testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e)) + if len(r) == len(d.e) { + for _, j in r { + testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j]) + } + } + } + + { + v := "" + r, err := os.split_path_list(v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r) + } + { + v := "a" + r, err := os.split_path_list(v, context.allocator) + testing.expectf(t, err == nil, "Expected err to be nil, got %v", err) + defer delete_split(r) + testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r)) + if len(r) == 1 { + testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0]) + } + } +} + +@(private) +delete_split :: proc(s: []string) { + for part in s { + delete(part) + } + delete(s) } \ No newline at end of file From 940105dc19f70c5ed7efc0d8957b76461616b7fa Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 14:40:17 +0100 Subject: [PATCH 41/59] Convert fmt_js.odin to not use `core:os*` --- core/fmt/fmt_js.odin | 109 +++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 62 deletions(-) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index de9e12cdc..458a6303c 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,9 +1,7 @@ #+build js package fmt -import "core:bufio" -import "core:io" -import os "core:os/os2" +import "core:strings" foreign import "odin_env" @@ -12,90 +10,77 @@ foreign odin_env { write :: proc "contextless" (fd: u32, p: []byte) --- } -@(private="file") -write_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - if mode == .Write { - fd := u32(uintptr(stream_data)) - write(fd, p) - return i64(len(p)), nil - } - return 0, .Unsupported -} +stdout :: u32(1) +stderr :: u32(2) @(private="file") -stdout := io.Writer{ - procedure = write_stream_proc, - data = rawptr(uintptr(1)), -} -@(private="file") -stderr := io.Writer{ - procedure = write_stream_proc, - data = rawptr(uintptr(2)), -} +BUF_SIZE :: 1024 @(private="file") -fd_to_writer :: proc(f: ^os.File, loc := #caller_location) -> io.Writer { - switch { - case f == os.stdout: return stdout - case f == os.stderr: return stderr - case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc) +// TODO: Find a way to grow this if necessary +buf: [BUF_SIZE]byte + +@(private="file") +get_fd :: proc(f: any, loc := #caller_location) -> (fd: u32) { + if _fd, _ok := f.(u32); _ok { + fd = _fd } + if fd != 1 && fd != 2 { + panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc) + } + return fd } // fprint formats using the default print settings and writes to fd -fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { - buf: [1024]byte - b: bufio.Writer - defer bufio.writer_flush(&b) - - bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) - w := bufio.writer_to_writer(&b) - return wprint(w, ..args, sep=sep, flush=flush) +// flush is ignored +fprint :: proc(f: any, args: ..any, sep := " ", flush := true, loc := #caller_location) -> (n: int) { + fd := get_fd(f) + s := bprint(buf[:], ..args, sep=sep) + n = len(s) + write(fd, transmute([]byte)s) + return n } -// fprintln formats using the default print settings and writes to fd -fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { - buf: [1024]byte - b: bufio.Writer - defer bufio.writer_flush(&b) - - bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) - - w := bufio.writer_to_writer(&b) - return wprintln(w, ..args, sep=sep, flush=flush) +// fprintln formats using the default print settings and writes to fd, followed by a newline +// flush is ignored +fprintln :: proc(f: any, args: ..any, sep := " ", flush := true, loc := #caller_location) -> (n: int) { + fd := get_fd(f) + s := bprintln(buf[:], ..args, sep=sep) + n = len(s) + write(fd, transmute([]byte)s) + return n } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { - buf: [1024]byte - b: bufio.Writer - defer bufio.writer_flush(&b) - - bufio.writer_init_with_buf(&b, fd_to_writer(f, loc), buf[:]) - - w := bufio.writer_to_writer(&b) - return wprintf(w, fmt, ..args, flush=flush, newline=newline) +// flush is ignored +fprintf :: proc(f: any, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> (n: int) { + fd := get_fd(f) + s := bprintf(buf[:], fmt, ..args, newline=newline) + n = len(s) + write(fd, transmute([]byte)s) + return n } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { +// flush is ignored +fprintfln :: proc(f: any, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { return fprintf(f, fmt, ..args, flush=flush, newline=true, loc=loc) } // print formats using the default print settings and writes to stdout -print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } +print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(stdout, ..args, sep=sep, flush=flush) } // println formats using the default print settings and writes to stdout -println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } +println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(stdout, ..args, sep=sep, flush=flush) } // printf formats according to the specififed format string and writes to stdout -printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to stdout, followed by a newline. -printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to stderr -eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } +eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(stderr, ..args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } +eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(stderr, ..args, sep=sep, flush=flush) } // eprintf formats according to the specififed format string and writes to stderr -eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to stderr, followed by a newline. -eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stderr, fmt, ..args, flush=flush, newline=true) } \ No newline at end of file From 876935065f51b1bb5557cd0e7604d41b0fb9e42f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 14:50:33 +0100 Subject: [PATCH 42/59] remove import --- core/fmt/fmt_js.odin | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index 458a6303c..ccab15220 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,8 +1,6 @@ #+build js package fmt -import "core:strings" - foreign import "odin_env" @(private="file") From 6270b02b2c40e5f7469573ff4fecc1b092106e97 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 3 Nov 2025 10:58:09 +0100 Subject: [PATCH 43/59] Remove os2 mockup warning --- core/os/os2/doc.odin | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/os/os2/doc.odin b/core/os/os2/doc.odin index 86dcbc452..2ebdd0912 100644 --- a/core/os/os2/doc.odin +++ b/core/os/os2/doc.odin @@ -3,9 +3,4 @@ // // The package os interface is intended to be uniform across all operating systems. // Features not generally available appear in the system-specific packages under core:sys/*. -// -// -// IMPORTANT NOTE from Bill: This package is not fully complete yet but should give designers a better idea of the general -// interface and how to write things. This entire interface is subject to change, but mostly working still. -// When things are finalized, this message will be removed. package os2 From fb7ac1fb648e567d04626389b8113997fec4cae2 Mon Sep 17 00:00:00 2001 From: Laytan Date: Mon, 3 Nov 2025 19:27:02 +0100 Subject: [PATCH 44/59] new_os: vendor/libc --- vendor/libc-shim/stdio.odin | 66 ++++++----------------- vendor/libc/stdio_js.odin | 60 +++++++++++++++++++++ vendor/libc/stdio_os.odin | 104 ++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 49 deletions(-) create mode 100644 vendor/libc/stdio_js.odin create mode 100644 vendor/libc/stdio_os.odin diff --git a/vendor/libc-shim/stdio.odin b/vendor/libc-shim/stdio.odin index e269b8986..b47b3f166 100644 --- a/vendor/libc-shim/stdio.odin +++ b/vendor/libc-shim/stdio.odin @@ -4,93 +4,60 @@ package odin_libc import "base:runtime" import "core:c" -import "core:io" -import "core:os" import "core:strconv" import stb "vendor:stb/sprintf" -FILE :: uintptr +FILE :: rawptr EOF :: -1 @(require, linkage="strong", link_name="fopen") fopen :: proc "c" (path: cstring, mode: cstring) -> FILE { context = g_ctx - unimplemented("vendor/libc-shim: fopen") + return _fopen(path, mode) } @(require, linkage="strong", link_name="fseek") fseek :: proc "c" (file: FILE, offset: c.long, whence: i32) -> i32 { context = g_ctx - handle := os.Handle(file-1) - _, err := os.seek(handle, i64(offset), int(whence)) - if err != nil { - return -1 - } - return 0 + return _fseek(file, offset, whence) } @(require, linkage="strong", link_name="ftell") ftell :: proc "c" (file: FILE) -> c.long { context = g_ctx - handle := os.Handle(file-1) - off, err := os.seek(handle, 0, os.SEEK_CUR) - if err != nil { - return -1 - } - return c.long(off) + return _ftell(file) } @(require, linkage="strong", link_name="fclose") fclose :: proc "c" (file: FILE) -> i32 { context = g_ctx - handle := os.Handle(file-1) - if os.close(handle) != nil { - return -1 - } - return 0 + return _fclose(file) } @(require, linkage="strong", link_name="fread") fread :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint { context = g_ctx - handle := os.Handle(file-1) - n, _ := os.read(handle, buffer[:min(size, count)]) - return uint(max(0, n)) + return _fread(buffer, size, count, file) } @(require, linkage="strong", link_name="fwrite") fwrite :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint { context = g_ctx - handle := os.Handle(file-1) - n, _ := os.write(handle, buffer[:min(size, count)]) - return uint(max(0, n)) + return _fwrite(buffer, size, count, file) } @(require, linkage="strong", link_name="putchar") putchar :: proc "c" (char: c.int) -> c.int { context = g_ctx - - n, err := os.write_byte(os.stdout, byte(char)) - if n == 0 || err != nil { - return EOF - } - return char + return _putchar(char) } @(require, linkage="strong", link_name="getchar") getchar :: proc "c" () -> c.int { - when #defined(os.stdin) { - ret: [1]byte - n, err := os.read(os.stdin, ret[:]) - if n == 0 || err != nil { - return EOF - } - return c.int(ret[0]) - } else { - return EOF - } + context = g_ctx + return _getchar() } @(require, linkage="strong", link_name="vsnprintf") @@ -109,8 +76,6 @@ vsprintf :: proc "c" (buf: [^]byte, fmt: cstring, args: ^c.va_list) -> i32 { vfprintf :: proc "c" (file: FILE, fmt: cstring, args: ^c.va_list) -> i32 { context = g_ctx - handle := os.Handle(file-1) - MAX_STACK :: 4096 buf: []byte @@ -133,12 +98,15 @@ vfprintf :: proc "c" (file: FILE, fmt: cstring, args: ^c.va_list) -> i32 { delete(buf) } - _, err := io.write_full(os.stream_from_handle(handle), buf) - if err != nil { - return -1 + written: i32 + for len(buf) > 0 { + n := _fwrite(raw_data(buf), size_of(byte), len(buf), file) + if n == 0 { break } + buf = buf[n:] + written += i32(n) } - return i32(len(buf)) + return written } /* diff --git a/vendor/libc/stdio_js.odin b/vendor/libc/stdio_js.odin new file mode 100644 index 000000000..2382ed449 --- /dev/null +++ b/vendor/libc/stdio_js.odin @@ -0,0 +1,60 @@ +#+private +package odin_libc + +import "core:c" + +foreign import "odin_env" + +_fopen :: proc(path, mode: cstring) -> FILE { + unimplemented("vendor/libc: fopen in JS") +} + +_fseek :: proc(file: FILE, offset: c.long, whence: i32) -> i32 { + unimplemented("vendor/libc: fseek in JS") +} + +_ftell :: proc(file: FILE) -> c.long { + unimplemented("vendor/libc: ftell in JS") +} + +_fclose :: proc(file: FILE) -> i32 { + unimplemented("vendor/libc: fclose in JS") +} + +_fread :: proc(buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint { + unimplemented("vendor/libc: fread in JS") +} + +_fwrite :: proc(buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint { + fd, ok := __fd(file) + if !ok { + return 0 + } + + __write(fd, buffer[:size*count]) + return count +} + +_putchar :: proc(char: c.int) -> c.int { + __write(1, {byte(char)}) + return char +} + +_getchar :: proc() -> c.int { + return EOF +} + +@(private="file") +foreign odin_env { + @(link_name="write") + __write :: proc "contextless" (fd: u32, p: []byte) --- +} + +@(private="file") +__fd :: proc(file: FILE) -> (u32, bool) { + switch (uint(uintptr(file))) { + case 2: return 1, true // stdout + case 3: return 2, true // stderr + case: return 0, false + } +} diff --git a/vendor/libc/stdio_os.odin b/vendor/libc/stdio_os.odin new file mode 100644 index 000000000..db40fb250 --- /dev/null +++ b/vendor/libc/stdio_os.odin @@ -0,0 +1,104 @@ +#+build !freestanding +#+build !js +package odin_libc + +import "core:io" +import "core:c" +import os "core:os/os2" + +_fopen :: proc(path, _mode: cstring) -> FILE { + flags: os.File_Flags + + mode := string(_mode) + if len(mode) > 1 { + switch mode[0] { + case 'r': + flags += {.Read} + case 'w': + flags += {.Write, .Create, .Trunc} + case 'a': + flags += {.Write, .Create, .Append} + case: + return nil + } + + if len(mode) > 1 && mode[1] == '+' { + flags += {.Write, .Read} + } else if len(mode) > 2 && mode[1] == 'b' && mode[2] == '+' { + flags += {.Write, .Read} + } + } + + file, err := os.open(string(path), flags, os.Permissions_Read_Write_All) + if err != nil { + return nil + } + + return FILE(file) +} + +_fseek :: proc(_file: FILE, offset: c.long, whence: i32) -> i32 { + file := __file(_file) + if _, err := os.seek(file, i64(offset), io.Seek_From(whence)); err != nil { + return -1 + } + + return 0 +} + +_ftell :: proc(_file: FILE) -> c.long { + file := __file(_file) + pos, err := os.seek(file, 0, .Current) + if err != nil { + return -1 + } + + return c.long(pos) +} + +_fclose :: proc(_file: FILE) -> i32 { + file := __file(_file) + if err := os.close(file); err != nil { + return EOF + } + + return 0 +} + +_fread :: proc(buffer: [^]byte, size: uint, count: uint, _file: FILE) -> uint { + file := __file(_file) + n, _ := os.read(file, buffer[:size*count]) + return uint(max(0, n)) / size +} + +_fwrite :: proc(buffer: [^]byte, size: uint, count: uint, _file: FILE) -> uint { + file := __file(_file) + n, _ := os.write(file, buffer[:size*count]) + return uint(max(0, n)) / size +} + +_putchar :: proc(char: c.int) -> c.int { + n, err := os.write_byte(os.stdout, byte(char)) + if n == 0 || err != nil { + return EOF + } + return char +} + +_getchar :: proc() -> c.int { + ret: [1]byte + n, err := os.read(os.stdin, ret[:]) + if n == 0 || err != nil { + return EOF + } + return c.int(ret[0]) +} + +@(private="file") +__file :: proc(file: FILE) -> ^os.File { + switch (uint(uintptr(file))) { + case 2: return os.stdout + case 3: return os.stderr + case: return (^os.File)(file) + } +} From 470a245f5fc927fa9d04da0adcbcd57574d4067f Mon Sep 17 00:00:00 2001 From: Laytan Date: Mon, 3 Nov 2025 19:51:42 +0100 Subject: [PATCH 45/59] new_os: core/prof/spall --- core/prof/spall/spall.odin | 29 +++++++++++++++++------------ core/prof/spall/spall_linux.odin | 8 +++----- core/prof/spall/spall_unix.odin | 14 ++++---------- core/prof/spall/spall_windows.odin | 14 ++++---------- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index dc53dc3dc..b079c2eb2 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -1,8 +1,9 @@ package spall -import "core:os" -import "core:time" -import "base:intrinsics" +import "base:intrinsics" + +import os "core:os/os2" +import "core:time" // File Format @@ -64,7 +65,9 @@ NAME_EVENT_MAX :: size_of(Name_Event) + 255 Context :: struct { precise_time: bool, timestamp_scale: f64, - fd: os.Handle, + + file: ^os.File, + fd: uintptr, } Buffer :: struct { @@ -79,18 +82,20 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok { - fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600) + file, err := os.open(filename, {.Write, .Append, .Create, .Trunc}, {.Read_User, .Write_User}) if err != nil { return } - ctx.fd = fd + ctx.file = file + ctx.fd = os.fd(file) + ctx.precise_time = precise_time ctx.timestamp_scale = timestamp_scale temp := [size_of(Manual_Stream_Header)]u8{} _build_stream_header(temp[:], ctx.timestamp_scale) - os.write(ctx.fd, temp[:]) + write(ctx.fd, temp[:]) ok = true return } @@ -109,7 +114,7 @@ context_destroy :: proc(ctx: ^Context) { return } - os.close(ctx.fd) + os.close(ctx.file) ctx^ = Context{} } @@ -288,12 +293,12 @@ _buffer_name_process :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Process) } -@(no_instrumentation) -write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) { - return _write(fd, buf) +@(no_instrumentation, private="package") +write :: proc "contextless" (fd: uintptr, buf: []byte) { + _write(fd, buf) } -@(no_instrumentation) +@(no_instrumentation, private="package") tick_now :: proc "contextless" () -> (ns: i64) { return _tick_now() } diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin index 8060af448..207bd8471 100644 --- a/core/prof/spall/spall_linux.odin +++ b/core/prof/spall/spall_linux.odin @@ -1,19 +1,17 @@ #+private package spall -// Only for types and constants. -import "core:os" - // Package is `#+no-instrumentation`, safe to use. import "core:sys/linux" MAX_RW :: 0x7fffffff @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { +_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ { + n: int for n < len(data) { chunk := data[:min(len(data), MAX_RW)] - n += linux.write(linux.Fd(fd), chunk) or_return + n += linux.write(linux.Fd(fd), chunk) or_break } return } diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index 455245aad..2f1e356ee 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -2,29 +2,23 @@ #+build darwin, freebsd, openbsd, netbsd package spall -// Only for types. -import "core:os" - import "core:sys/posix" MAX_RW :: 0x7fffffff @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { - if len(data) == 0 { - return 0, nil - } - +_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ { + n: int for n < len(data) { chunk := data[:min(len(data), MAX_RW)] written := posix.write(posix.FD(fd), raw_data(chunk), len(chunk)) if written < 0 { - return n, os.get_last_error() + return } n += written } - return n, nil + return } // NOTE(tetra): "RAW" means: Not adjusted by NTP. diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin index 11e216b63..2d059dc4d 100644 --- a/core/prof/spall/spall_windows.odin +++ b/core/prof/spall/spall_windows.odin @@ -1,20 +1,13 @@ #+private package spall -// Only for types. -import "core:os" - // Package is `#+no-instrumentation`, safe to use. import win32 "core:sys/windows" MAX_RW :: 1<<30 @(no_instrumentation) -_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { - if len(data) == 0 { - return 0, nil - } - +_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ { single_write_length: win32.DWORD total_write: i64 length := i64(len(data)) @@ -25,11 +18,12 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #n e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) if single_write_length <= 0 || !e { - return int(total_write), os.get_last_error() + return } total_write += i64(single_write_length) } - return int(total_write), nil + + return } @(no_instrumentation) From 38def33c95bec05ad5687fa074d7e26f2b1f923b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:40:51 +0100 Subject: [PATCH 46/59] Fix more merge conflicts. --- core/net/dns_os.odin | 14 +++++++------- core/testing/signal_handler_libc.odin | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/net/dns_os.odin b/core/net/dns_os.odin index 19db0097a..8528dad00 100644 --- a/core/net/dns_os.odin +++ b/core/net/dns_os.odin @@ -2,12 +2,13 @@ #+private package net -import "core:os" +import os "core:os/os2" load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { context.allocator = allocator - res := os.read_entire_file_from_filename(resolv_conf_path) or_return + res, err := os.read_entire_file(resolv_conf_path, allocator) + if err != nil { return } defer delete(res) resolv_str := string(res) @@ -15,10 +16,9 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato } load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { - hosts_file, err := os.open(hosts_file_path) + handle, err := os.open(hosts_file_path) if err != nil { return } - defer os.close(hosts_file) - - return parse_hosts(os.stream_from_handle(hosts_file), allocator) -} + defer os.close(handle) + return parse_hosts(os.to_stream(handle), allocator) +} \ No newline at end of file diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 593d2c285..badee802d 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -91,7 +91,7 @@ stop_test_callback :: proc "c" (sig: libc.int) { advisory_a := ` The test runner's main thread has caught an unrecoverable error (signal ` advisory_b := `) and will now forcibly terminate. -This is a dire bug and should be reported to the Odin developers. +Unless you terminated the tests yourself, this could be a dire bug and should be reported to the Odin developers. ` libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr) libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr) From 89fccd6056a4ff188f82ff8364aedcbf5b832bb1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:17:39 +0100 Subject: [PATCH 47/59] One more fix. --- core/text/table/utility.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin index 99fd7d7ca..e361d0f5f 100644 --- a/core/text/table/utility.odin +++ b/core/text/table/utility.odin @@ -5,7 +5,7 @@ import os "core:os/os2" import "core:strings" stdio_writer :: proc() -> io.Writer { - return os.stdout.stream + return os.to_stream(os.stdout) } strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer { From 29b6a7ddfe39dce2b932066a12c3e69d809b7965 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:21:01 +0100 Subject: [PATCH 48/59] Remove unused import --- core/net/dns.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/net/dns.odin b/core/net/dns.odin index 2d7a04896..6af18798b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -23,7 +23,6 @@ package net @(require) import "base:runtime" -import os "core:os/os2" import "core:bufio" import "core:io" import "core:math/rand" From 674f0e04745d0f679ce540ec4ae025910a612757 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:28:04 +0100 Subject: [PATCH 49/59] Fix doc tests --- tests/documentation/documentation_tester.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 55e40963d..6694de709 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -301,7 +301,7 @@ _spawn_pipe_reader :: proc() { read_to_null_byte := 0 finished_reading := false for ! finished_reading { - just_read, err := io.read(_read_pipe.stream, _out_buffer[n_read:], &n_read); if err != .None { + just_read, err := io.read(os.to_stream(_read_pipe), _out_buffer[n_read:], &n_read); if err != .None { panic("We got an IO error!") } for b in _out_buffer[n_read - just_read: n_read] { @@ -450,13 +450,13 @@ main :: proc() { continue } defer os.close(test_file_handle) - fmt.wprintf(test_file_handle.stream, "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:]) + fmt.wprintf(os.to_stream(test_file_handle), "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:]) fmt.println("Done") } strings.write_string(&test_runner, ` - fmt.wprintfln(_old_stdout.stream, "Passes: %v. Fails: %v", _good_count, _bad_count) + fmt.wprintfln(os.to_stream(_old_stdout), "Passes: %v. Fails: %v", _good_count, _bad_count) if _bad_count > 0 { fmt.eprintln("One or more tests failed") os.exit(1) From b16ca5a986c129500285c238bbe1d3e0dd44e75e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:51:49 +0100 Subject: [PATCH 50/59] fix libc-shim --- vendor/{libc => libc-shim}/stdio_js.odin | 0 vendor/{libc => libc-shim}/stdio_os.odin | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename vendor/{libc => libc-shim}/stdio_js.odin (100%) rename vendor/{libc => libc-shim}/stdio_os.odin (100%) diff --git a/vendor/libc/stdio_js.odin b/vendor/libc-shim/stdio_js.odin similarity index 100% rename from vendor/libc/stdio_js.odin rename to vendor/libc-shim/stdio_js.odin diff --git a/vendor/libc/stdio_os.odin b/vendor/libc-shim/stdio_os.odin similarity index 100% rename from vendor/libc/stdio_os.odin rename to vendor/libc-shim/stdio_os.odin From 5bf30b2d562dd7b746a351dce333914e2ca958c8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 14:13:20 +0100 Subject: [PATCH 51/59] Fix os2 file_wasi --- core/os/os2/file_wasi.odin | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin index 1333ecf20..78aa90699 100644 --- a/core/os/os2/file_wasi.odin +++ b/core/os/os2/file_wasi.odin @@ -41,7 +41,6 @@ init_std_files :: proc "contextless" () { data = impl, procedure = _file_stream_proc, } - impl.file.fstat = _fstat return &impl.file } @@ -222,7 +221,6 @@ _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) - data = impl, procedure = _file_stream_proc, } - impl.file.fstat = _fstat return &impl.file, nil } @@ -434,7 +432,7 @@ _exists :: proc(path: string) -> bool { return true } -_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { +_file_stream_proc :: proc(stream_data: rawptr, mode: File_Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From, allocator: runtime.Allocator) -> (n: i64, err: Error) { f := (^File_Impl)(stream_data) fd := f.fd @@ -562,6 +560,10 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Query: return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + case .Fstat: + err = file_stream_fstat_utility(f, p, allocator) + return + case: return 0, .Unsupported } From 8ed264680b1f3f94b6aa5176824d4ccadfc30322 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 16:39:56 +0100 Subject: [PATCH 52/59] Remove all `core:os` imports from JS targets Fix `local_tz_name` on FreeBSD. --- core/crypto/hash/hash_js.odin | 10 +++ core/crypto/hash/hash_os.odin | 1 + core/encoding/hxa/hxa_os.odin | 34 ++++++++ core/encoding/hxa/read.odin | 15 ---- core/encoding/hxa/write.odin | 16 ---- core/encoding/ini/ini.odin | 15 ---- core/encoding/ini/ini_os.odin | 20 +++++ core/encoding/xml/xml_os.odin | 18 +++++ core/encoding/xml/xml_reader.odin | 14 ---- core/log/file_console_logger.odin | 1 + core/math/big/radix.odin | 57 ------------- core/math/big/radix_os.odin | 79 +++++++++++++++++++ core/mem/virtual/common.odin | 16 ++++ core/mem/virtual/file.odin | 17 +--- .../{internal.odin => internal_os.odin} | 2 + core/terminal/terminal.odin | 4 +- core/text/i18n/gettext.odin | 8 -- core/text/i18n/i18_js.odin | 18 +++++ core/text/i18n/i18n.odin | 11 --- core/text/i18n/i18n_os.odin | 38 +++++++++ core/text/i18n/qt_linguist.odin | 6 -- core/text/regex/common/common.odin | 2 + core/text/regex/common/debugging.odin | 7 -- core/text/regex/common/os.odin | 17 ++++ core/text/table/utility.odin | 2 + core/time/timezone/tz_os.odin | 19 +++++ core/time/timezone/tz_unix.odin | 2 +- core/time/timezone/tzdate.odin | 4 - core/time/timezone/tzif.odin | 11 --- examples/all/all_js.odin | 7 +- tests/core/time/test_core_time.odin | 4 +- 31 files changed, 288 insertions(+), 187 deletions(-) create mode 100644 core/crypto/hash/hash_js.odin create mode 100644 core/encoding/hxa/hxa_os.odin create mode 100644 core/encoding/ini/ini_os.odin create mode 100644 core/encoding/xml/xml_os.odin create mode 100644 core/math/big/radix_os.odin create mode 100644 core/mem/virtual/common.odin rename core/terminal/{internal.odin => internal_os.odin} (98%) create mode 100644 core/text/i18n/i18_js.odin create mode 100644 core/text/i18n/i18n_os.odin create mode 100644 core/text/regex/common/os.odin create mode 100644 core/time/timezone/tz_os.odin diff --git a/core/crypto/hash/hash_js.odin b/core/crypto/hash/hash_js.odin new file mode 100644 index 000000000..99309b944 --- /dev/null +++ b/core/crypto/hash/hash_js.odin @@ -0,0 +1,10 @@ +#+build js +package crypto_hash + +hash :: proc { + hash_stream, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin index 28db9c61f..5155623cb 100644 --- a/core/crypto/hash/hash_os.odin +++ b/core/crypto/hash/hash_os.odin @@ -1,4 +1,5 @@ #+build !freestanding +#+build !js package crypto_hash import "core:io" diff --git a/core/encoding/hxa/hxa_os.odin b/core/encoding/hxa/hxa_os.odin new file mode 100644 index 000000000..c033bdca8 --- /dev/null +++ b/core/encoding/hxa/hxa_os.odin @@ -0,0 +1,34 @@ +#+build !freestanding +#+build !js +package encoding_hxa + +import os "core:os/os2" + +read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { + context.allocator = allocator + + data, data_err := os.read_entire_file(filename, allocator, loc) + if data_err != nil { + err = .Unable_To_Read_File + delete(data, allocator) + return + } + file, err = read(data, filename, print_error, allocator) + file.backing = data + return +} + +write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) { + required := required_write_size(file) + buf, alloc_err := make([]byte, required) + if alloc_err == .Out_Of_Memory { + return .Failed_File_Write + } + defer delete(buf) + + write_internal(&Writer{data = buf}, file) + if os.write_entire_file(filepath, buf) != nil { + err =.Failed_File_Write + } + return +} diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index 1f1c3633e..1721bf7fc 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -1,7 +1,6 @@ package encoding_hxa import "core:fmt" -import os "core:os/os2" import "core:mem" Read_Error :: enum { @@ -11,20 +10,6 @@ Read_Error :: enum { Unable_To_Read_File, } -read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { - context.allocator = allocator - - data, data_err := os.read_entire_file(filename, allocator, loc) - if data_err != nil { - err = .Unable_To_Read_File - delete(data, allocator) - return - } - file, err = read(data, filename, print_error, allocator) - file.backing = data - return -} - read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { Reader :: struct { filename: string, diff --git a/core/encoding/hxa/write.odin b/core/encoding/hxa/write.odin index e8ef9a139..cbf9c7cb6 100644 --- a/core/encoding/hxa/write.odin +++ b/core/encoding/hxa/write.odin @@ -1,6 +1,5 @@ package encoding_hxa -import os "core:os/os2" import "core:mem" Write_Error :: enum { @@ -9,21 +8,6 @@ Write_Error :: enum { Failed_File_Write, } -write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) { - required := required_write_size(file) - buf, alloc_err := make([]byte, required) - if alloc_err == .Out_Of_Memory { - return .Failed_File_Write - } - defer delete(buf) - - write_internal(&Writer{data = buf}, file) - if os.write_entire_file(filepath, buf) != nil { - err =.Failed_File_Write - } - return -} - write :: proc(buf: []byte, file: File) -> (n: int, err: Write_Error) { required := required_write_size(file) if len(buf) < required { diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 644ce8937..8bf6c6c9a 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -6,7 +6,6 @@ import "base:intrinsics" import "core:strings" import "core:strconv" import "core:io" -import os "core:os/os2" import "core:fmt" _ :: fmt @@ -120,20 +119,6 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options return } -load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { - data, data_err := os.read_entire_file(path, allocator) - defer delete(data, allocator) - if data_err != nil { - return - } - m, err = load_map_from_string(string(data), allocator, options) - ok = err == nil - defer if !ok { - delete_map(m) - } - return -} - save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) { b := strings.builder_make(allocator) _, _ = write_map(strings.to_writer(&b), m) diff --git a/core/encoding/ini/ini_os.odin b/core/encoding/ini/ini_os.odin new file mode 100644 index 000000000..619a0e2a6 --- /dev/null +++ b/core/encoding/ini/ini_os.odin @@ -0,0 +1,20 @@ +#+build !freestanding +#+build !js +package encoding_ini + +import "base:runtime" +import os "core:os/os2" + +load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { + data, data_err := os.read_entire_file(path, allocator) + defer delete(data, allocator) + if data_err != nil { + return + } + m, err = load_map_from_string(string(data), allocator, options) + ok = err == nil + defer if !ok { + delete_map(m) + } + return +} diff --git a/core/encoding/xml/xml_os.odin b/core/encoding/xml/xml_os.odin new file mode 100644 index 000000000..8c7f6cccf --- /dev/null +++ b/core/encoding/xml/xml_os.odin @@ -0,0 +1,18 @@ +#+build !freestanding +#+build !js +package encoding_xml + +import os "core:os/os2" + +// Load an XML file +load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { + context.allocator = allocator + options := options + + data, data_err := os.read_entire_file(filename, allocator) + if data_err != nil { return {}, .File_Error } + + options.flags += { .Input_May_Be_Modified } + + return parse_bytes(data, options, filename, error_handler, allocator) +} diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index 798eb3f87..6d068466b 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -14,7 +14,6 @@ import "core:bytes" import "core:encoding/entity" import "base:intrinsics" import "core:mem" -import os "core:os/os2" import "core:strings" likely :: intrinsics.expect @@ -373,19 +372,6 @@ parse_string :: proc(data: string, options := DEFAULT_OPTIONS, path := "", error parse :: proc { parse_string, parse_bytes } -// Load an XML file -load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { - context.allocator = allocator - options := options - - data, data_err := os.read_entire_file(filename, allocator) - if data_err != nil { return {}, .File_Error } - - options.flags += { .Input_May_Be_Modified } - - return parse_bytes(data, options, filename, error_handler, allocator) -} - destroy :: proc(doc: ^Document, allocator := context.allocator) { context.allocator = allocator if doc == nil { return } diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index a9b073c18..819d494e9 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,5 +1,6 @@ #+build !freestanding #+build !orca +#+build !js package log import "base:runtime" diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index c0ea8cb79..0d57bc071 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -17,7 +17,6 @@ package math_big import "base:intrinsics" import "core:mem" -import os "core:os/os2" /* This version of `itoa` allocates on behalf of the caller. The caller must free the string. @@ -386,63 +385,7 @@ radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false, allocator := con return size, nil } -/* - We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions. - LibTomMath allows exporting/importing to/from a file in ASCII, but it doesn't support a much more compact representation in binary, even though it has several pack functions int_to_bytes_* (which I expanded upon and wrote Python interoperable versions of as well), and (un)pack, which is GMP compatible. - Someone could implement their own read/write binary int procedures, of course. - - Could be worthwhile to add a canonical binary file representation with an optional small header that says it's an Odin big.Int, big.Rat or Big.Float, byte count for each component that follows, flag for big/little endian and a flag that says a checksum exists at the end of the file. - For big.Rat and big.Float the header couldn't be optional, because we'd have no way to distinguish where the components end. -*/ - -/* - Read an Int from an ASCII file. -*/ -internal_int_read_from_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - /* - We can either read the entire file at once, or read a bunch at a time and keep multiplying by the radix. - For now, we'll read the entire file. Eventually we'll replace this with a copy that duplicates the logic - of `atoi` so we don't need to read the entire file. - */ - res, res_err := os.read_entire_file(filename, allocator) - defer delete(res, allocator) - - if res_err != nil { - return .Cannot_Read_File - } - - as := string(res) - return atoi(a, as, radix) -} - -/* - Write an Int to an ASCII file. -*/ -internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - /* - For now we'll convert the Int using itoa and writing the result in one go. - If we want to preserve memory we could duplicate the itoa logic and write backwards. - */ - - as := itoa(a, radix) or_return - defer delete(as) - - l := len(as) - assert(l > 0) - - data := transmute([]u8)mem.Raw_Slice{ - data = raw_data(as), - len = l, - } - - write_err := os.write_entire_file(filename, data, truncate=true) - return nil if write_err == nil else .Cannot_Write_File -} /* Calculate the size needed for `internal_int_pack`. diff --git a/core/math/big/radix_os.odin b/core/math/big/radix_os.odin new file mode 100644 index 000000000..8269a4338 --- /dev/null +++ b/core/math/big/radix_os.odin @@ -0,0 +1,79 @@ +#+build !freestanding +#+build !js +package math_big + +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's license. + + An arbitrary precision mathematics implementation in Odin. + For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3. + The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. + + This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa). + + TODO: + - Use Barrett reduction for non-powers-of-two. + - Also look at extracting and splatting several digits at once. +*/ + +import "core:mem" +import os "core:os/os2" + +/* + We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions. + + LibTomMath allows exporting/importing to/from a file in ASCII, but it doesn't support a much more compact representation in binary, even though it has several pack functions int_to_bytes_* (which I expanded upon and wrote Python interoperable versions of as well), and (un)pack, which is GMP compatible. + Someone could implement their own read/write binary int procedures, of course. + + Could be worthwhile to add a canonical binary file representation with an optional small header that says it's an Odin big.Int, big.Rat or Big.Float, byte count for each component that follows, flag for big/little endian and a flag that says a checksum exists at the end of the file. + For big.Rat and big.Float the header couldn't be optional, because we'd have no way to distinguish where the components end. +*/ + +/* + Read an Int from an ASCII file. +*/ +internal_int_read_from_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + /* + We can either read the entire file at once, or read a bunch at a time and keep multiplying by the radix. + For now, we'll read the entire file. Eventually we'll replace this with a copy that duplicates the logic + of `atoi` so we don't need to read the entire file. + */ + res, res_err := os.read_entire_file(filename, allocator) + defer delete(res, allocator) + + if res_err != nil { + return .Cannot_Read_File + } + + as := string(res) + return atoi(a, as, radix) +} + +/* + Write an Int to an ASCII file. +*/ +internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + /* + For now we'll convert the Int using itoa and writing the result in one go. + If we want to preserve memory we could duplicate the itoa logic and write backwards. + */ + + as := itoa(a, radix) or_return + defer delete(as) + + l := len(as) + assert(l > 0) + + data := transmute([]u8)mem.Raw_Slice{ + data = raw_data(as), + len = l, + } + + write_err := os.write_entire_file(filename, data, truncate=true) + return nil if write_err == nil else .Cannot_Write_File +} \ No newline at end of file diff --git a/core/mem/virtual/common.odin b/core/mem/virtual/common.odin new file mode 100644 index 000000000..ad8505c89 --- /dev/null +++ b/core/mem/virtual/common.odin @@ -0,0 +1,16 @@ +package mem_virtual + +Map_File_Error :: enum { + None, + Open_Failure, + Stat_Failure, + Negative_Size, + Too_Large_Size, + Map_Failure, +} + +Map_File_Flag :: enum u32 { + Read, + Write, +} +Map_File_Flags :: distinct bit_set[Map_File_Flag; u32] \ No newline at end of file diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin index c532335c5..b156f2af4 100644 --- a/core/mem/virtual/file.odin +++ b/core/mem/virtual/file.odin @@ -1,22 +1,9 @@ +#+build !freestanding +#+build !js package mem_virtual import os "core:os/os2" -Map_File_Error :: enum { - None, - Open_Failure, - Stat_Failure, - Negative_Size, - Too_Large_Size, - Map_Failure, -} - -Map_File_Flag :: enum u32 { - Read, - Write, -} -Map_File_Flags :: distinct bit_set[Map_File_Flag; u32] - map_file :: proc{ map_file_from_path, map_file_from_file, diff --git a/core/terminal/internal.odin b/core/terminal/internal_os.odin similarity index 98% rename from core/terminal/internal.odin rename to core/terminal/internal_os.odin index 956726b8a..841803766 100644 --- a/core/terminal/internal.odin +++ b/core/terminal/internal_os.odin @@ -1,3 +1,5 @@ +#+build !freestanding +#+build !js #+private package terminal diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin index d24b963d3..86d928649 100644 --- a/core/terminal/terminal.odin +++ b/core/terminal/terminal.odin @@ -1,8 +1,6 @@ // Interaction with the command line interface (`CLI`) of the system. package terminal -import os "core:os/os2" - /* This describes the range of colors that a terminal is capable of supporting. */ @@ -21,7 +19,7 @@ This is normally true for `os.stdout` and `os.stderr` unless they are redirected to a file. */ @(require_results) -is_terminal :: proc(f: ^os.File) -> bool { +is_terminal :: proc(f: $T) -> bool { return _is_terminal(f) } diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin index 4aa86a35d..b0e3dae67 100644 --- a/core/text/i18n/gettext.odin +++ b/core/text/i18n/gettext.odin @@ -125,14 +125,6 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur return } -parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { - data := read_file(filename, allocator) or_return - defer delete(data) - return parse_mo_from_bytes(data, options, pluralizer, allocator) -} - -parse_mo :: proc { parse_mo_file, parse_mo_from_bytes } - @(private) read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) { if len(data) < size_of(u32) { return 0, .Premature_EOF } diff --git a/core/text/i18n/i18_js.odin b/core/text/i18n/i18_js.odin new file mode 100644 index 000000000..743718942 --- /dev/null +++ b/core/text/i18n/i18_js.odin @@ -0,0 +1,18 @@ +#+build freestanding +#+build js +package i18n +/* + Internationalization helpers. + + Copyright 2021-2022 Jeroen van Rijn . + Made available under Odin's license. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +import os "core:os/os2" + +@(private) +parse_qt :: proc { parse_qt_linguist_from_bytes } + +parse_mo :: proc { parse_mo_from_bytes } \ No newline at end of file diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin index 8b107a8cd..148fe229f 100644 --- a/core/text/i18n/i18n.odin +++ b/core/text/i18n/i18n.odin @@ -8,8 +8,6 @@ package i18n List of contributors: Jeroen van Rijn: Initial implementation. */ -import "base:runtime" -import os "core:os/os2" import "core:strings" // Currently active catalog. @@ -231,13 +229,4 @@ destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) delete(catalog.k_v) strings.intern_destroy(&catalog.intern) free(catalog) -} - -@(private) -read_file :: proc(filename: string, allocator: runtime.Allocator) -> (data: []u8, err: Error) { - file_data, file_err := os.read_entire_file(filename, allocator) - if file_err != nil { - return {}, .File_Error - } - return file_data, nil } \ No newline at end of file diff --git a/core/text/i18n/i18n_os.odin b/core/text/i18n/i18n_os.odin new file mode 100644 index 000000000..db82a9cf6 --- /dev/null +++ b/core/text/i18n/i18n_os.odin @@ -0,0 +1,38 @@ +#+build !freestanding +#+build !js +package i18n +/* + Internationalization helpers. + + Copyright 2021-2022 Jeroen van Rijn . + Made available under Odin's license. + + List of contributors: + Jeroen van Rijn: Initial implementation. +*/ +import "base:runtime" +import os "core:os/os2" + +@(private) +read_file :: proc(filename: string, allocator: runtime.Allocator) -> (data: []u8, err: Error) { + file_data, file_err := os.read_entire_file(filename, allocator) + if file_err != nil { + return {}, .File_Error + } + return file_data, nil +} + +parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + data := read_file(filename, allocator) or_return + return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) +} + +parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes } + +parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { + data := read_file(filename, allocator) or_return + defer delete(data) + return parse_mo_from_bytes(data, options, pluralizer, allocator) +} + +parse_mo :: proc { parse_mo_file, parse_mo_from_bytes } \ No newline at end of file diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 78fe2712d..9c5791b67 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -154,9 +154,3 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI return } -parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { - data := read_file(filename, allocator) or_return - return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) -} - -parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes } \ No newline at end of file diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin index 24b44833f..cfbbe53ba 100644 --- a/core/text/regex/common/common.odin +++ b/core/text/regex/common/common.odin @@ -14,6 +14,8 @@ MAX_CAPTURE_GROUPS :: max(#config(ODIN_REGEX_MAX_CAPTURE_GROUPS, 10), 10) MAX_PROGRAM_SIZE :: int(max(i16)) MAX_CLASSES :: int(max(u8)) +ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false) + Flag :: enum u8 { // Multiline: treat `^` and `$` as if they also match newlines. Multiline, diff --git a/core/text/regex/common/debugging.odin b/core/text/regex/common/debugging.odin index e3fcb8b7e..055bbd20d 100644 --- a/core/text/regex/common/debugging.odin +++ b/core/text/regex/common/debugging.odin @@ -8,16 +8,9 @@ package regex_common Feoramund: Initial implementation. */ -@require import os "core:os/os2" import "core:io" import "core:strings" -ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false) - -when ODIN_DEBUG_REGEX { - debug_stream := os.stderr.stream -} - write_padded_hex :: proc(w: io.Writer, #any_int n, zeroes: int) { sb := strings.builder_make() defer strings.builder_destroy(&sb) diff --git a/core/text/regex/common/os.odin b/core/text/regex/common/os.odin new file mode 100644 index 000000000..1d38d687c --- /dev/null +++ b/core/text/regex/common/os.odin @@ -0,0 +1,17 @@ +#+build !freestanding +#+build !js +package regex_common + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +@require import os "core:os/os2" + +when ODIN_DEBUG_REGEX { + debug_stream := os.stderr.stream +} \ No newline at end of file diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin index e361d0f5f..db5ae4602 100644 --- a/core/text/table/utility.odin +++ b/core/text/table/utility.odin @@ -1,3 +1,5 @@ +#+build !freestanding +#+build !js package text_table import "core:io" diff --git a/core/time/timezone/tz_os.odin b/core/time/timezone/tz_os.odin new file mode 100644 index 000000000..2ab7cfa6c --- /dev/null +++ b/core/time/timezone/tz_os.odin @@ -0,0 +1,19 @@ +#+build !freestanding +#+build !js +package timezone + +import os "core:os/os2" +import "core:time/datetime" + +load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { + tzif_data, tzif_err := os.read_entire_file(filename, allocator) + if tzif_err != nil { + return nil, false + } + defer delete(tzif_data, allocator) + return parse_tzif(tzif_data, region_name, allocator) +} + +region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return load_tzif_file(file_path, reg, allocator) +} \ No newline at end of file diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index 32590f177..60a20e57c 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -14,7 +14,7 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: path, err := os.get_absolute_path(orig_localtime_path, allocator) if err != nil { // If we can't find /etc/localtime, fallback to UTC - if err == .ENOENT { + if err == .Not_Exist { str, err2 := strings.clone("UTC", allocator) if err2 != nil { return } return str, true diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin index f01553573..29e8ad25c 100644 --- a/core/time/timezone/tzdate.odin +++ b/core/time/timezone/tzdate.odin @@ -10,10 +10,6 @@ region_load :: proc(reg: string, allocator := context.allocator) -> (out_reg: ^ return _region_load(reg, allocator) } -region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { - return load_tzif_file(file_path, reg, allocator) -} - region_load_from_buffer :: proc(buffer: []u8, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { return parse_tzif(buffer, reg, allocator) } diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin index 7a7023c6c..3b92364ac 100644 --- a/core/time/timezone/tzif.odin +++ b/core/time/timezone/tzif.odin @@ -3,7 +3,6 @@ package timezone import "base:intrinsics" import "core:slice" import "core:strings" -import os "core:os/os2" import "core:strconv" import "core:time/datetime" @@ -67,16 +66,6 @@ tzif_data_block_size :: proc(hdr: ^TZif_Header, version: TZif_Version) -> (block int(hdr.isutcnt), true } - -load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { - tzif_data, tzif_err := os.read_entire_file(filename, allocator) - if tzif_err != nil { - return nil, false - } - defer delete(tzif_data, allocator) - return parse_tzif(tzif_data, region_name, allocator) -} - @private is_alphabetic :: proc(ch: u8) -> bool { // ('A' -> 'Z') || ('a' -> 'z') diff --git a/examples/all/all_js.odin b/examples/all/all_js.odin index ee006ae86..bb7ed7fa1 100644 --- a/examples/all/all_js.odin +++ b/examples/all/all_js.odin @@ -10,7 +10,7 @@ package all @(require) import "core:compress" @(require) import "core:compress/shoco" -@(require) import "core:compress/gzip" +// @(require) import "core:compress/gzip" @(require) import "core:compress/zlib" @(require) import "core:container/avl" @@ -99,13 +99,13 @@ package all @(require) import "core:mem" @(require) import "core:mem/tlsf" -@(require) import "core:mem/virtual" +// Not supported on JS +// @(require) import "core:mem/virtual" @(require) import "core:odin/ast" @(require) import doc_format "core:odin/doc-format" @(require) import "core:odin/tokenizer" -@(require) import "core:os" @(require) import "core:path/slashpath" @(require) import "core:relative" @@ -130,6 +130,7 @@ package all @(require) import "core:text/match" @(require) import "core:text/regex" @(require) import "core:text/scanner" +// Not supported on JS, uses `core:mem/virtual`. @(require) import "core:text/table" @(require) import "core:thread" diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index cd2b19fb8..6e5e47696 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -2,6 +2,7 @@ package test_core_time import "core:testing" import "core:time" +@(require) import "core:log" import dt "core:time/datetime" import tz "core:time/timezone" @@ -364,9 +365,10 @@ test_convert_timezone_roundtrip :: proc(t: ^testing.T) { std_dt, _ := dt.components_to_datetime(2024, 11, 4, 23, 47, 0) local_tz, local_load_ok := tz.region_load("local") - testing.expectf(t, local_load_ok, "Failed to load local timezone") defer tz.region_destroy(local_tz) + testing.expectf(t, local_load_ok, "Failed to load local timezone") + edm_tz, edm_load_ok := tz.region_load("America/Edmonton") testing.expectf(t, edm_load_ok, "Failed to load America/Edmonton timezone") defer tz.region_destroy(edm_tz) From e7dbabf6681e4e6bcae33398e939c2c9c3cdc879 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 15:50:21 +0100 Subject: [PATCH 53/59] core:os -> core:os/old && core:os/os2 -> core:os --- core/compress/gzip/doc.odin | 10 +- core/compress/gzip/gzip.odin | 12 +- core/crypto/hash/hash_os.odin | 2 +- core/encoding/csv/doc.odin | 2 +- core/encoding/hxa/hxa_os.odin | 2 +- core/encoding/ini/ini_os.odin | 2 +- core/encoding/xml/xml_os.odin | 2 +- core/flags/errors.odin | 2 +- core/flags/example/example.odin | 2 +- core/flags/internal_rtti.odin | 24 +- core/flags/internal_validation.odin | 16 +- core/flags/util.odin | 6 +- core/fmt/fmt_os.odin | 8 +- core/image/bmp/bmp_os.odin | 4 +- core/image/general_os.odin | 2 +- core/image/jpeg/jpeg_os.odin | 2 +- core/image/netpbm/netpbm_os.odin | 2 +- core/image/png/png_os.odin | 2 +- core/image/qoi/qoi_os.odin | 4 +- core/image/tga/tga_os.odin | 4 +- core/log/file_console_logger.odin | 14 +- core/math/big/radix_os.odin | 2 +- core/mem/virtual/doc.odin | 4 +- core/mem/virtual/file.odin | 2 +- core/net/dns_os.odin | 2 +- core/odin/parser/parse_files.odin | 14 +- core/os/{os2 => }/allocators.odin | 0 core/os/{os2 => }/dir.odin | 4 +- core/os/{os2 => }/dir_js.odin | 0 core/os/{os2 => }/dir_linux.odin | 0 core/os/{os2 => }/dir_posix.odin | 0 core/os/{os2 => }/dir_posix_darwin.odin | 0 core/os/{os2 => }/dir_walker.odin | 6 +- core/os/{os2 => }/dir_wasi.odin | 0 core/os/dir_windows.odin | 196 ++++---- core/os/{os2 => }/doc.odin | 0 core/os/{os2 => }/env.odin | 0 core/os/{os2 => }/env_js.odin | 0 core/os/{os2 => }/env_linux.odin | 0 core/os/{os2 => }/env_posix.odin | 0 core/os/{os2 => }/env_wasi.odin | 0 core/os/env_windows.odin | 162 +++---- core/os/errors.odin | 257 ++--------- core/os/{os2 => }/errors_js.odin | 0 core/os/{os2 => }/errors_linux.odin | 0 core/os/{os2 => }/errors_posix.odin | 0 core/os/{os2 => }/errors_wasi.odin | 0 core/os/{os2 => }/errors_windows.odin | 0 core/os/{os2 => }/file.odin | 0 core/os/{os2 => }/file_js.odin | 0 core/os/{os2 => }/file_linux.odin | 0 core/os/{os2 => }/file_posix.odin | 0 core/os/{os2 => }/file_posix_darwin.odin | 0 core/os/{os2 => }/file_posix_freebsd.odin | 0 core/os/{os2 => }/file_posix_netbsd.odin | 0 core/os/{os2 => }/file_posix_other.odin | 0 core/os/{os2 => }/file_stream.odin | 0 core/os/{os2 => }/file_util.odin | 0 core/os/{os2 => }/file_wasi.odin | 0 core/os/{os2 => }/file_windows.odin | 0 core/os/{os2 => }/heap.odin | 0 core/os/{os2 => }/heap_js.odin | 0 core/os/{os2 => }/heap_linux.odin | 0 core/os/{os2 => }/heap_posix.odin | 0 core/os/{os2 => }/heap_wasi.odin | 0 core/os/{os2 => }/heap_windows.odin | 0 core/os/{os2 => }/internal_util.odin | 0 core/os/{ => old}/dir_unix.odin | 0 core/os/old/dir_windows.odin | 114 +++++ core/os/old/env_windows.odin | 140 ++++++ core/os/old/errors.odin | 318 +++++++++++++ core/os/{ => old}/os.odin | 0 core/os/{ => old}/os_darwin.odin | 0 core/os/{ => old}/os_essence.odin | 0 core/os/{ => old}/os_freebsd.odin | 0 core/os/{ => old}/os_freestanding.odin | 0 core/os/{ => old}/os_haiku.odin | 0 core/os/{ => old}/os_js.odin | 0 core/os/{ => old}/os_linux.odin | 0 core/os/{ => old}/os_netbsd.odin | 0 core/os/{ => old}/os_openbsd.odin | 0 core/os/{ => old}/os_wasi.odin | 0 core/os/{ => old}/os_windows.odin | 0 core/os/old/stat.odin | 33 ++ core/os/{ => old}/stat_unix.odin | 0 core/os/old/stat_windows.odin | 303 ++++++++++++ core/os/{ => old}/stream.odin | 0 core/os/os2/dir_windows.odin | 144 ------ core/os/os2/env_windows.odin | 142 ------ core/os/os2/errors.odin | 147 ------ core/os/os2/stat.odin | 117 ----- core/os/os2/stat_windows.odin | 393 ---------------- core/os/{os2 => }/path.odin | 0 core/os/{os2 => }/path_darwin.odin | 0 core/os/{os2 => }/path_freebsd.odin | 0 core/os/{os2 => }/path_js.odin | 0 core/os/{os2 => }/path_linux.odin | 0 core/os/{os2 => }/path_netbsd.odin | 0 core/os/{os2 => }/path_openbsd.odin | 0 core/os/{os2 => }/path_posix.odin | 0 core/os/{os2 => }/path_posixfs.odin | 0 core/os/{os2 => }/path_wasi.odin | 0 core/os/{os2 => }/path_windows.odin | 0 core/os/{os2 => }/pipe.odin | 0 core/os/{os2 => }/pipe_js.odin | 0 core/os/{os2 => }/pipe_linux.odin | 0 core/os/{os2 => }/pipe_posix.odin | 0 core/os/{os2 => }/pipe_wasi.odin | 0 core/os/{os2 => }/pipe_windows.odin | 0 core/os/{os2 => }/process.odin | 0 core/os/{os2 => }/process_freebsd.odin | 0 core/os/{os2 => }/process_js.odin | 0 core/os/{os2 => }/process_linux.odin | 0 core/os/{os2 => }/process_netbsd.odin | 0 core/os/{os2 => }/process_openbsd.odin | 0 core/os/{os2 => }/process_posix.odin | 0 core/os/{os2 => }/process_posix_darwin.odin | 0 core/os/{os2 => }/process_posix_other.odin | 0 core/os/{os2 => }/process_wasi.odin | 0 core/os/{os2 => }/process_windows.odin | 0 core/os/stat.odin | 116 ++++- core/os/{os2 => }/stat_js.odin | 0 core/os/{os2 => }/stat_linux.odin | 0 core/os/{os2 => }/stat_posix.odin | 0 core/os/{os2 => }/stat_wasi.odin | 0 core/os/stat_windows.odin | 432 +++++++++++------- core/os/{os2 => }/temp_file.odin | 0 core/os/{os2 => }/temp_file_js.odin | 0 core/os/{os2 => }/temp_file_linux.odin | 0 core/os/{os2 => }/temp_file_posix.odin | 0 core/os/{os2 => }/temp_file_wasi.odin | 0 core/os/{os2 => }/temp_file_windows.odin | 0 core/os/{os2 => }/user.odin | 0 core/os/{os2 => }/user_posix.odin | 0 core/os/{os2 => }/user_windows.odin | 0 core/path/filepath/match.odin | 2 +- core/path/filepath/path.odin | 4 +- core/path/filepath/walk.odin | 8 +- core/prof/spall/spall.odin | 4 +- core/terminal/internal_os.odin | 6 +- core/terminal/terminal_posix.odin | 4 +- core/terminal/terminal_windows.odin | 6 +- core/testing/runner.odin | 36 +- core/testing/signal_handler_libc.odin | 10 +- core/text/i18n/i18_js.odin | 2 - core/text/i18n/i18n_os.odin | 2 +- core/text/regex/common/os.odin | 2 +- core/text/table/doc.odin | 10 +- core/text/table/utility.odin | 6 +- core/time/timezone/tz_os.odin | 2 +- core/time/timezone/tz_unix.odin | 8 +- core/unicode/tools/generate_entity_table.odin | 2 +- examples/all/all_main.odin | 2 +- tests/core/encoding/hxa/test_core_hxa.odin | 2 +- tests/core/flags/test_core_flags.odin | 22 +- tests/core/io/test_core_io.odin | 14 +- tests/core/nbio/fs.odin | 8 +- tests/core/nbio/nbio.odin | 12 +- tests/core/normal.odin | 2 +- tests/core/os/{os2 => }/dir.odin | 22 +- tests/core/os/{os2 => }/file.odin | 6 +- tests/core/os/{ => old}/os.odin | 4 +- tests/core/os/{os2 => }/path.odin | 24 +- tests/core/os/{os2 => }/process.odin | 8 +- tests/core/sys/kqueue/structs.odin | 6 +- tests/documentation/documentation_tester.odin | 4 +- vendor/OpenGL/helpers.odin | 2 +- vendor/fontstash/fontstash_os.odin | 4 +- vendor/libc-shim/stdio_os.odin | 6 +- 169 files changed, 1716 insertions(+), 1718 deletions(-) rename core/os/{os2 => }/allocators.odin (100%) rename core/os/{os2 => }/dir.odin (99%) rename core/os/{os2 => }/dir_js.odin (100%) rename core/os/{os2 => }/dir_linux.odin (100%) rename core/os/{os2 => }/dir_posix.odin (100%) rename core/os/{os2 => }/dir_posix_darwin.odin (100%) rename core/os/{os2 => }/dir_walker.odin (98%) rename core/os/{os2 => }/dir_wasi.odin (100%) rename core/os/{os2 => }/doc.odin (100%) rename core/os/{os2 => }/env.odin (100%) rename core/os/{os2 => }/env_js.odin (100%) rename core/os/{os2 => }/env_linux.odin (100%) rename core/os/{os2 => }/env_posix.odin (100%) rename core/os/{os2 => }/env_wasi.odin (100%) rename core/os/{os2 => }/errors_js.odin (100%) rename core/os/{os2 => }/errors_linux.odin (100%) rename core/os/{os2 => }/errors_posix.odin (100%) rename core/os/{os2 => }/errors_wasi.odin (100%) rename core/os/{os2 => }/errors_windows.odin (100%) rename core/os/{os2 => }/file.odin (100%) rename core/os/{os2 => }/file_js.odin (100%) rename core/os/{os2 => }/file_linux.odin (100%) rename core/os/{os2 => }/file_posix.odin (100%) rename core/os/{os2 => }/file_posix_darwin.odin (100%) rename core/os/{os2 => }/file_posix_freebsd.odin (100%) rename core/os/{os2 => }/file_posix_netbsd.odin (100%) rename core/os/{os2 => }/file_posix_other.odin (100%) rename core/os/{os2 => }/file_stream.odin (100%) rename core/os/{os2 => }/file_util.odin (100%) rename core/os/{os2 => }/file_wasi.odin (100%) rename core/os/{os2 => }/file_windows.odin (100%) rename core/os/{os2 => }/heap.odin (100%) rename core/os/{os2 => }/heap_js.odin (100%) rename core/os/{os2 => }/heap_linux.odin (100%) rename core/os/{os2 => }/heap_posix.odin (100%) rename core/os/{os2 => }/heap_wasi.odin (100%) rename core/os/{os2 => }/heap_windows.odin (100%) rename core/os/{os2 => }/internal_util.odin (100%) rename core/os/{ => old}/dir_unix.odin (100%) create mode 100644 core/os/old/dir_windows.odin create mode 100644 core/os/old/env_windows.odin create mode 100644 core/os/old/errors.odin rename core/os/{ => old}/os.odin (100%) rename core/os/{ => old}/os_darwin.odin (100%) rename core/os/{ => old}/os_essence.odin (100%) rename core/os/{ => old}/os_freebsd.odin (100%) rename core/os/{ => old}/os_freestanding.odin (100%) rename core/os/{ => old}/os_haiku.odin (100%) rename core/os/{ => old}/os_js.odin (100%) rename core/os/{ => old}/os_linux.odin (100%) rename core/os/{ => old}/os_netbsd.odin (100%) rename core/os/{ => old}/os_openbsd.odin (100%) rename core/os/{ => old}/os_wasi.odin (100%) rename core/os/{ => old}/os_windows.odin (100%) create mode 100644 core/os/old/stat.odin rename core/os/{ => old}/stat_unix.odin (100%) create mode 100644 core/os/old/stat_windows.odin rename core/os/{ => old}/stream.odin (100%) delete mode 100644 core/os/os2/dir_windows.odin delete mode 100644 core/os/os2/env_windows.odin delete mode 100644 core/os/os2/errors.odin delete mode 100644 core/os/os2/stat.odin delete mode 100644 core/os/os2/stat_windows.odin rename core/os/{os2 => }/path.odin (100%) rename core/os/{os2 => }/path_darwin.odin (100%) rename core/os/{os2 => }/path_freebsd.odin (100%) rename core/os/{os2 => }/path_js.odin (100%) rename core/os/{os2 => }/path_linux.odin (100%) rename core/os/{os2 => }/path_netbsd.odin (100%) rename core/os/{os2 => }/path_openbsd.odin (100%) rename core/os/{os2 => }/path_posix.odin (100%) rename core/os/{os2 => }/path_posixfs.odin (100%) rename core/os/{os2 => }/path_wasi.odin (100%) rename core/os/{os2 => }/path_windows.odin (100%) rename core/os/{os2 => }/pipe.odin (100%) rename core/os/{os2 => }/pipe_js.odin (100%) rename core/os/{os2 => }/pipe_linux.odin (100%) rename core/os/{os2 => }/pipe_posix.odin (100%) rename core/os/{os2 => }/pipe_wasi.odin (100%) rename core/os/{os2 => }/pipe_windows.odin (100%) rename core/os/{os2 => }/process.odin (100%) rename core/os/{os2 => }/process_freebsd.odin (100%) rename core/os/{os2 => }/process_js.odin (100%) rename core/os/{os2 => }/process_linux.odin (100%) rename core/os/{os2 => }/process_netbsd.odin (100%) rename core/os/{os2 => }/process_openbsd.odin (100%) rename core/os/{os2 => }/process_posix.odin (100%) rename core/os/{os2 => }/process_posix_darwin.odin (100%) rename core/os/{os2 => }/process_posix_other.odin (100%) rename core/os/{os2 => }/process_wasi.odin (100%) rename core/os/{os2 => }/process_windows.odin (100%) rename core/os/{os2 => }/stat_js.odin (100%) rename core/os/{os2 => }/stat_linux.odin (100%) rename core/os/{os2 => }/stat_posix.odin (100%) rename core/os/{os2 => }/stat_wasi.odin (100%) rename core/os/{os2 => }/temp_file.odin (100%) rename core/os/{os2 => }/temp_file_js.odin (100%) rename core/os/{os2 => }/temp_file_linux.odin (100%) rename core/os/{os2 => }/temp_file_posix.odin (100%) rename core/os/{os2 => }/temp_file_wasi.odin (100%) rename core/os/{os2 => }/temp_file_windows.odin (100%) rename core/os/{os2 => }/user.odin (100%) rename core/os/{os2 => }/user_posix.odin (100%) rename core/os/{os2 => }/user_windows.odin (100%) rename tests/core/os/{os2 => }/dir.odin (82%) rename tests/core/os/{os2 => }/file.odin (90%) rename tests/core/os/{ => old}/os.odin (97%) rename tests/core/os/{os2 => }/path.odin (97%) rename tests/core/os/{os2 => }/process.odin (84%) diff --git a/core/compress/gzip/doc.odin b/core/compress/gzip/doc.odin index 82eaa6f35..e4b1929dd 100644 --- a/core/compress/gzip/doc.odin +++ b/core/compress/gzip/doc.odin @@ -2,11 +2,11 @@ A small `GZIP` unpacker. Example: - import "core:bytes" - import os "core:os/os2" - import "core:compress" - import "core:compress/gzip" - import "core:fmt" + import "core:bytes" + import "core:os" + import "core:compress" + import "core:compress/gzip" + import "core:fmt" // Small GZIP file with fextra, fname and fcomment present. @private diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 644a625e7..aedbe3a83 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -14,12 +14,12 @@ package compress_gzip to be the input to a complementary TAR implementation. */ -import "core:compress/zlib" -import "core:compress" -import os "core:os/os2" -import "core:io" -import "core:bytes" -import "core:hash" +import "core:compress/zlib" +import "core:compress" +import "core:os" +import "core:io" +import "core:bytes" +import "core:hash" Magic :: enum u16le { GZIP = 0x8b << 8 | 0x1f, diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin index 5155623cb..49c1a0ff8 100644 --- a/core/crypto/hash/hash_os.odin +++ b/core/crypto/hash/hash_os.odin @@ -3,7 +3,7 @@ package crypto_hash import "core:io" -import os "core:os/os2" +import "core:os" // `hash_file` will read the file provided by the given handle and return the // computed digest in a newly allocated slice. diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin index c6dae3005..1fb685602 100644 --- a/core/encoding/csv/doc.odin +++ b/core/encoding/csv/doc.odin @@ -6,7 +6,7 @@ Example: import "core:fmt" import "core:encoding/csv" - import os "core:os/os2" + import "core:os" // Requires keeping the entire CSV file in memory at once iterate_csv_from_string :: proc(filename: string) { diff --git a/core/encoding/hxa/hxa_os.odin b/core/encoding/hxa/hxa_os.odin index c033bdca8..17ad94819 100644 --- a/core/encoding/hxa/hxa_os.odin +++ b/core/encoding/hxa/hxa_os.odin @@ -2,7 +2,7 @@ #+build !js package encoding_hxa -import os "core:os/os2" +import "core:os" read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator diff --git a/core/encoding/ini/ini_os.odin b/core/encoding/ini/ini_os.odin index 619a0e2a6..22c6bf7b3 100644 --- a/core/encoding/ini/ini_os.odin +++ b/core/encoding/ini/ini_os.odin @@ -3,7 +3,7 @@ package encoding_ini import "base:runtime" -import os "core:os/os2" +import "core:os" load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { data, data_err := os.read_entire_file(path, allocator) diff --git a/core/encoding/xml/xml_os.odin b/core/encoding/xml/xml_os.odin index 8c7f6cccf..1e94572c6 100644 --- a/core/encoding/xml/xml_os.odin +++ b/core/encoding/xml/xml_os.odin @@ -2,7 +2,7 @@ #+build !js package encoding_xml -import os "core:os/os2" +import "core:os" // Load an XML file load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { diff --git a/core/flags/errors.odin b/core/flags/errors.odin index d0caa1427..efe4cb6c4 100644 --- a/core/flags/errors.odin +++ b/core/flags/errors.odin @@ -2,7 +2,7 @@ package flags import "base:runtime" import "core:net" -import os "core:os/os2" +import "core:os" Parse_Error_Reason :: enum { None, diff --git a/core/flags/example/example.odin b/core/flags/example/example.odin index 6e74c7dcc..6ace3d852 100644 --- a/core/flags/example/example.odin +++ b/core/flags/example/example.odin @@ -4,7 +4,7 @@ import "base:runtime" import "core:flags" import "core:fmt" import "core:net" -import os "core:os/os2" +import "core:os" import "core:time/datetime" diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index 07481a89b..d5e8726e2 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -1,18 +1,18 @@ #+private package flags -import "base:intrinsics" -import "base:runtime" -import "core:fmt" -import "core:mem" -import "core:net" -@(require) import os "core:os/os2" -import "core:reflect" -import "core:strconv" -import "core:strings" -@require import "core:time" -@require import "core:time/datetime" -import "core:unicode/utf8" +import "base:intrinsics" +import "base:runtime" +import "core:fmt" +import "core:mem" +import "core:net" +@(require) import "core:os" +import "core:reflect" +import "core:strconv" +import "core:strings" +@(require) import "core:time" +@(require) import "core:time/datetime" +import "core:unicode/utf8" @(optimization_mode="favor_size") parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool { diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index dc19f3084..6f9016a21 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -1,14 +1,14 @@ #+private package flags -@require import "base:runtime" -@require import "core:container/bit_array" -@require import "core:fmt" -@require import "core:mem" -@require import os "core:os/os2" -@require import "core:reflect" -@require import "core:strconv" -@require import "core:strings" +@require import "base:runtime" +@require import "core:container/bit_array" +@require import "core:fmt" +@require import "core:mem" +@require import "core:os" +@require import "core:reflect" +@require import "core:strconv" +@require import "core:strings" // This proc is used to assert that `T` meets the expectations of the library. @(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT) diff --git a/core/flags/util.odin b/core/flags/util.odin index 20e40cab5..0d18fa196 100644 --- a/core/flags/util.odin +++ b/core/flags/util.odin @@ -1,9 +1,9 @@ package flags -import "core:fmt" -@require import os "core:os/os2" +import "core:fmt" +@require import "core:os" @require import "core:path/filepath" -import "core:strings" +import "core:strings" /* Parse any arguments into an annotated struct or exit if there was an error. diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 7ce945a0f..0305b5bac 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -3,10 +3,10 @@ #+build !orca package fmt -import "base:runtime" -import os "core:os/os2" -import "core:io" -import "core:bufio" +import "base:runtime" +import "core:os" +import "core:io" +import "core:bufio" // NOTE(Jeroen): The other option is to deprecate `fprint*` and make it an alias for `wprint*`, using File.stream directly. diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin index 971750fda..1aa1d63de 100644 --- a/core/image/bmp/bmp_os.odin +++ b/core/image/bmp/bmp_os.odin @@ -1,8 +1,8 @@ #+build !js package core_image_bmp -import os "core:os/os2" -import "core:bytes" +import "core:os" +import "core:bytes" load :: proc{load_from_file, load_from_bytes, load_from_context} diff --git a/core/image/general_os.odin b/core/image/general_os.odin index e4de1c9a6..63d7c8d43 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -1,7 +1,7 @@ #+build !js package image -import os "core:os/os2" +import "core:os" load :: proc{ load_from_bytes, diff --git a/core/image/jpeg/jpeg_os.odin b/core/image/jpeg/jpeg_os.odin index aad172c91..6ba301d80 100644 --- a/core/image/jpeg/jpeg_os.odin +++ b/core/image/jpeg/jpeg_os.odin @@ -1,7 +1,7 @@ #+build !js package jpeg -import os "core:os/os2" +import "core:os" load :: proc{load_from_file, load_from_bytes, load_from_context} diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin index 82ad55f35..ae9029b54 100644 --- a/core/image/netpbm/netpbm_os.odin +++ b/core/image/netpbm/netpbm_os.odin @@ -1,7 +1,7 @@ #+build !js package netpbm -import os "core:os/os2" +import "core:os" load :: proc { load_from_file, diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin index c6a88fa52..5fc10cec4 100644 --- a/core/image/png/png_os.odin +++ b/core/image/png/png_os.odin @@ -1,7 +1,7 @@ #+build !js package png -import os "core:os/os2" +import "core:os" load :: proc{load_from_file, load_from_bytes, load_from_context} diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin index a65527d09..f2bf83cfc 100644 --- a/core/image/qoi/qoi_os.odin +++ b/core/image/qoi/qoi_os.odin @@ -1,8 +1,8 @@ #+build !js package qoi -import os "core:os/os2" -import "core:bytes" +import "core:os" +import "core:bytes" load :: proc{load_from_file, load_from_bytes, load_from_context} diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin index 2c103b34a..ba50439de 100644 --- a/core/image/tga/tga_os.odin +++ b/core/image/tga/tga_os.odin @@ -1,8 +1,8 @@ #+build !js package tga -import os "core:os/os2" -import "core:bytes" +import "core:os" +import "core:bytes" load :: proc{load_from_file, load_from_bytes, load_from_context} diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 819d494e9..47174719f 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -3,13 +3,13 @@ #+build !js package log -import "base:runtime" -import "core:fmt" -import "core:strings" -import os "core:os/os2" -import "core:terminal" -import "core:terminal/ansi" -import "core:time" +import "base:runtime" +import "core:fmt" +import "core:strings" +import "core:os" +import "core:terminal" +import "core:terminal/ansi" +import "core:time" Level_Headers := [?]string{ 0..<10 = "[DEBUG] --- ", diff --git a/core/math/big/radix_os.odin b/core/math/big/radix_os.odin index 8269a4338..50454b679 100644 --- a/core/math/big/radix_os.odin +++ b/core/math/big/radix_os.odin @@ -18,7 +18,7 @@ package math_big */ import "core:mem" -import os "core:os/os2" +import "core:os" /* We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions. diff --git a/core/mem/virtual/doc.odin b/core/mem/virtual/doc.odin index 249e22ee8..b5f0944c7 100644 --- a/core/mem/virtual/doc.odin +++ b/core/mem/virtual/doc.odin @@ -5,8 +5,8 @@ virtual.Arena usage Example: // Source: https://github.com/odin-lang/examples/blob/master/arena_allocator/arena_allocator.odin - import "core:fmt" - import os "core:os/os2" + import "core:fmt" + import "core:os" // virtual package implements a multi-purpose arena allocator. If you are on a // platform that does not support virtual memory, then there is also a similar diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin index b156f2af4..660210bbf 100644 --- a/core/mem/virtual/file.odin +++ b/core/mem/virtual/file.odin @@ -2,7 +2,7 @@ #+build !js package mem_virtual -import os "core:os/os2" +import "core:os" map_file :: proc{ map_file_from_path, diff --git a/core/net/dns_os.odin b/core/net/dns_os.odin index 8528dad00..ad9724d37 100644 --- a/core/net/dns_os.odin +++ b/core/net/dns_os.odin @@ -2,7 +2,7 @@ #+private package net -import os "core:os/os2" +import "core:os" load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { context.allocator = allocator diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 93c282d35..2ea47ca89 100644 --- a/core/odin/parser/parse_files.odin +++ b/core/odin/parser/parse_files.odin @@ -1,12 +1,12 @@ package odin_parser -import "core:odin/tokenizer" -import "core:odin/ast" -import "core:path/filepath" -import "core:fmt" -import os "core:os/os2" -import "core:slice" -import "core:strings" +import "core:odin/tokenizer" +import "core:odin/ast" +import "core:path/filepath" +import "core:fmt" +import "core:os" +import "core:slice" +import "core:strings" collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) { NO_POS :: tokenizer.Pos{} diff --git a/core/os/os2/allocators.odin b/core/os/allocators.odin similarity index 100% rename from core/os/os2/allocators.odin rename to core/os/allocators.odin diff --git a/core/os/os2/dir.odin b/core/os/dir.odin similarity index 99% rename from core/os/os2/dir.odin rename to core/os/dir.odin index f63754273..9ad5f451e 100644 --- a/core/os/os2/dir.odin +++ b/core/os/dir.odin @@ -160,8 +160,8 @@ extend its lifetime. Example: package main - import "core:fmt" - import os "core:os/os2" + import "core:fmt" + import "core:os" main :: proc() { f, oerr := os.open("core") diff --git a/core/os/os2/dir_js.odin b/core/os/dir_js.odin similarity index 100% rename from core/os/os2/dir_js.odin rename to core/os/dir_js.odin diff --git a/core/os/os2/dir_linux.odin b/core/os/dir_linux.odin similarity index 100% rename from core/os/os2/dir_linux.odin rename to core/os/dir_linux.odin diff --git a/core/os/os2/dir_posix.odin b/core/os/dir_posix.odin similarity index 100% rename from core/os/os2/dir_posix.odin rename to core/os/dir_posix.odin diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/dir_posix_darwin.odin similarity index 100% rename from core/os/os2/dir_posix_darwin.odin rename to core/os/dir_posix_darwin.odin diff --git a/core/os/os2/dir_walker.odin b/core/os/dir_walker.odin similarity index 98% rename from core/os/os2/dir_walker.odin rename to core/os/dir_walker.odin index ba5342cf8..4dce884a8 100644 --- a/core/os/os2/dir_walker.odin +++ b/core/os/dir_walker.odin @@ -136,9 +136,9 @@ If an error occurred opening a directory, you may get zero'd info struct and Example: package main - import "core:fmt" - import "core:strings" - import os "core:os/os2" + import "core:fmt" + import "core:strings" + import "core:os" main :: proc() { w := os.walker_create("core") diff --git a/core/os/os2/dir_wasi.odin b/core/os/dir_wasi.odin similarity index 100% rename from core/os/os2/dir_wasi.odin rename to core/os/dir_wasi.odin diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 40f4b9e9b..a4dadca75 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -1,114 +1,144 @@ -package os +#+private +package os2 -import win32 "core:sys/windows" -import "core:strings" import "base:runtime" +import "core:time" +import win32 "core:sys/windows" + +@(private="file") +find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return + + handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0) + defer win32.CloseHandle(handle) + + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0) + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + + if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) { + #assert(size_of(fi.inode) == size_of(file_id_info.FileId)) + #assert(size_of(fi.inode) == 16) + runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16) + } + + return +} + +Read_Directory_Iterator_Impl :: struct { + find_data: win32.WIN32_FIND_DATAW, + find_handle: win32.HANDLE, + path: string, + prev_fi: File_Info, + no_more_files: bool, +} + @(require_results) -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { - @(require_results) - find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) { - // Ignore "." and ".." - if d.cFileName[0] == '.' && d.cFileName[1] == 0 { +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + for !it.impl.no_more_files { + err: Error + file_info_delete(it.impl.prev_fi, file_allocator()) + it.impl.prev_fi = {} + + fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) + if err != nil { + read_directory_iterator_set_error(it, it.impl.path, err) return } - if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { - return - } - path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:]) or_else ""}) - fi.fullpath = path - fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { - fi.mode |= 0o444 - } else { - fi.mode |= 0o666 + if fi.name != "" { + it.impl.prev_fi = fi + ok = true + index = it.index + it.index += 1 } - is_sym := false - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { - is_sym = false - } else { - is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT - } - - if is_sym { - fi.mode |= File_Mode_Sym_Link - } else { - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - fi.mode |= 0o111 | File_Mode_Dir + if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) { + read_directory_iterator_set_error(it, it.impl.path, e) } - - // fi.mode |= file_type_mode(h); + it.impl.no_more_files = true } + if ok { + return + } + } + return +} - windows_set_file_info_times(&fi, d) +_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + it.impl.no_more_files = false - fi.is_dir = fi.mode & File_Mode_Dir != 0 + if f == nil || f.impl == nil { + read_directory_iterator_set_error(it, "", .Invalid_File) return } - if fd == 0 { - return nil, ERROR_INVALID_HANDLE + it.f = f + impl := (^File_Impl)(f.impl) + + // NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx. + if it.impl.find_handle != nil { + win32.FindClose(it.impl.find_handle) + } + if it.impl.path != "" { + delete(it.impl.path, file_allocator()) } - context.allocator = allocator - - h := win32.HANDLE(fd) - - dir_fi, _ := file_info_from_get_file_information_by_handle("", h) - if !dir_fi.is_dir { - return nil, .Not_Dir - } - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - - wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return - if len(wpath) == 0 { + if !is_directory(impl.name) { + read_directory_iterator_set_error(it, impl.name, .Invalid_Dir) return } - dfi := make([dynamic]File_Info, 0, size) or_return + wpath := string16(impl.wname) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return + wpath_search := make([]u16, len(wpath)+3, temp_allocator) copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+2] = 0 - path := cleanpath_from_buf(wpath) - defer delete(path) - - find_data := &win32.WIN32_FIND_DATAW{} - find_handle := win32.FindFirstFileW(cstring16(raw_data(wpath_search)), find_data) - if find_handle == win32.INVALID_HANDLE_VALUE { - err = get_last_error() - return dfi[:], err + it.impl.find_handle = win32.FindFirstFileW(cstring16(raw_data(wpath_search)), &it.impl.find_data) + if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { + read_directory_iterator_set_error(it, impl.name, _get_platform_error()) + return } - defer win32.FindClose(find_handle) - for n != 0 { - fi: File_Info - fi = find_data_to_file_info(path, find_data) - if fi.name != "" { - append(&dfi, fi) - n -= 1 - } - - if !win32.FindNextFileW(find_handle, find_data) { - e := get_last_error() - if e == ERROR_NO_MORE_FILES { - break - } - return dfi[:], e - } + defer if it.err.err != nil { + win32.FindClose(it.impl.find_handle) } - return dfi[:], nil + err: Error + it.impl.path, err = _cleanpath_from_buf(wpath, file_allocator()) + if err != nil { + read_directory_iterator_set_error(it, impl.name, err) + } + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it.f == nil { + return + } + file_info_delete(it.impl.prev_fi, file_allocator()) + delete(it.impl.path, file_allocator()) + win32.FindClose(it.impl.find_handle) } diff --git a/core/os/os2/doc.odin b/core/os/doc.odin similarity index 100% rename from core/os/os2/doc.odin rename to core/os/doc.odin diff --git a/core/os/os2/env.odin b/core/os/env.odin similarity index 100% rename from core/os/os2/env.odin rename to core/os/env.odin diff --git a/core/os/os2/env_js.odin b/core/os/env_js.odin similarity index 100% rename from core/os/os2/env_js.odin rename to core/os/env_js.odin diff --git a/core/os/os2/env_linux.odin b/core/os/env_linux.odin similarity index 100% rename from core/os/os2/env_linux.odin rename to core/os/env_linux.odin diff --git a/core/os/os2/env_posix.odin b/core/os/env_posix.odin similarity index 100% rename from core/os/os2/env_posix.odin rename to core/os/env_posix.odin diff --git a/core/os/os2/env_wasi.odin b/core/os/env_wasi.odin similarity index 100% rename from core/os/os2/env_wasi.odin rename to core/os/env_wasi.odin diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index ef658b0a1..d389f8860 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -1,30 +1,37 @@ -package os +#+private +package os2 import win32 "core:sys/windows" import "base:runtime" -// lookup_env gets the value of the environment variable named by the key -// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true -// Otherwise the returned value will be empty and the boolean will be false -// NOTE: the value will be allocated with the supplied allocator -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } - wkey := win32.utf8_to_wstring(key) - n := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { - return "", false - } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + wkey, _ := win32_utf8_to_wstring(key, temp_allocator) + + n := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + return "", true + } + + b := make([]u16, n+1, temp_allocator) - b, _ := make([dynamic]u16, n, context.temp_allocator) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } return "", false } - value, _ = win32.utf16_to_utf8(b[:n], allocator) + + value = win32_utf16_to_utf8(string16(b[:n]), allocator) or_else "" found = true return } @@ -33,7 +40,7 @@ lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: // Note that it is limited to environment names and values of 512 utf-16 values each // due to the necessary utf-8 <> utf-16 conversion. @(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { key_buf: [513]u16 wkey := win32.utf8_to_wstring(key_buf[:], key) if wkey == nil { @@ -57,78 +64,28 @@ lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) return value, nil } -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} -// get_env retrieves the value of the environment variable named by the key -// It returns the value, which will be empty if the variable is not present -// To distinguish between an empty value and an unset value, use lookup_env -// NOTE: the value will be allocated with the supplied allocator -@(require_results) -get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { - value, _ = lookup_env(key, allocator) - return -} - -@(require_results) -get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { - value, _ = lookup_env(buf, key) - return -} -get_env :: proc{get_env_alloc, get_env_buf} - - -// set_env sets the value of the environment variable named by the key -set_env :: proc(key, value: string) -> Error { - k := win32.utf8_to_wstring(key) - v := win32.utf8_to_wstring(value) +_set_env :: proc(key, value: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k := win32_utf8_to_wstring(key, temp_allocator) or_return + v := win32_utf8_to_wstring(value, temp_allocator) or_return if !win32.SetEnvironmentVariableW(k, v) { - return get_last_error() + return _get_platform_error() } return nil } -// unset_env unsets a single environment variable -unset_env :: proc(key: string) -> Error { - k := win32.utf8_to_wstring(key) - if !win32.SetEnvironmentVariableW(k, nil) { - return get_last_error() - } - return nil +_unset_env :: proc(key: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k, _ := win32_utf8_to_wstring(key, temp_allocator) + return bool(win32.SetEnvironmentVariableW(k, nil)) } -// environ returns a copy of strings representing the environment, in the form "key=value" -// NOTE: the slice of strings and the strings with be allocated using the supplied allocator -@(require_results) -environ :: proc(allocator := context.allocator) -> []string { - envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) - if envs == nil { - return nil - } - defer win32.FreeEnvironmentStringsW(envs) - - r, err := make([dynamic]string, 0, 50, allocator) - if err != nil { - return nil - } - for from, i := 0, 0; true; i += 1 { - if c := envs[i]; c == 0 { - if i <= from { - break - } - append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "") - from = i + 1 - } - } - - return r[:] -} - - -// clear_env deletes all environment variables -clear_env :: proc() { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - envs := environ(context.temp_allocator) +_clear_env :: proc() { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + envs, _ := environ(temp_allocator) for env in envs { for j in 1.. string where intrinsics.type_is_enum(Platform_Error) { - if e == nil { - return "" - } - when ODIN_OS == .Darwin { - if s := string(_darwin_string_error(i32(e))); s != "" { - return s - } - } - - when ODIN_OS != .Linux { - @(require_results) - binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check { - n := len(array) - left, right := 0, n - for left < right { - mid := int(uint(left+right) >> 1) - if array[mid] < key { - left = mid+1 - } else { - // equal or greater - right = mid - } - } - return left, left < n && array[left] == key - } - - err := runtime.Type_Info_Enum_Value(e) - - ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum) - if idx, ok := binary_search(ti.values, err); ok { - return ti.names[idx] - } - } else { - @(rodata, static) - pe_strings := [Platform_Error]string{ - .NONE = "", - .EPERM = "Operation not permitted", - .ENOENT = "No such file or directory", - .ESRCH = "No such process", - .EINTR = "Interrupted system call", - .EIO = "Input/output error", - .ENXIO = "No such device or address", - .E2BIG = "Argument list too long", - .ENOEXEC = "Exec format error", - .EBADF = "Bad file descriptor", - .ECHILD = "No child processes", - .EAGAIN = "Resource temporarily unavailable", - .ENOMEM = "Cannot allocate memory", - .EACCES = "Permission denied", - .EFAULT = "Bad address", - .ENOTBLK = "Block device required", - .EBUSY = "Device or resource busy", - .EEXIST = "File exists", - .EXDEV = "Invalid cross-device link", - .ENODEV = "No such device", - .ENOTDIR = "Not a directory", - .EISDIR = "Is a directory", - .EINVAL = "Invalid argument", - .ENFILE = "Too many open files in system", - .EMFILE = "Too many open files", - .ENOTTY = "Inappropriate ioctl for device", - .ETXTBSY = "Text file busy", - .EFBIG = "File too large", - .ENOSPC = "No space left on device", - .ESPIPE = "Illegal seek", - .EROFS = "Read-only file system", - .EMLINK = "Too many links", - .EPIPE = "Broken pipe", - .EDOM = "Numerical argument out of domain", - .ERANGE = "Numerical result out of range", - .EDEADLK = "Resource deadlock avoided", - .ENAMETOOLONG = "File name too long", - .ENOLCK = "No locks available", - .ENOSYS = "Function not implemented", - .ENOTEMPTY = "Directory not empty", - .ELOOP = "Too many levels of symbolic links", - .EUNKNOWN_41 = "Unknown Error (41)", - .ENOMSG = "No message of desired type", - .EIDRM = "Identifier removed", - .ECHRNG = "Channel number out of range", - .EL2NSYNC = "Level 2 not synchronized", - .EL3HLT = "Level 3 halted", - .EL3RST = "Level 3 reset", - .ELNRNG = "Link number out of range", - .EUNATCH = "Protocol driver not attached", - .ENOCSI = "No CSI structure available", - .EL2HLT = "Level 2 halted", - .EBADE = "Invalid exchange", - .EBADR = "Invalid request descriptor", - .EXFULL = "Exchange full", - .ENOANO = "No anode", - .EBADRQC = "Invalid request code", - .EBADSLT = "Invalid slot", - .EUNKNOWN_58 = "Unknown Error (58)", - .EBFONT = "Bad font file format", - .ENOSTR = "Device not a stream", - .ENODATA = "No data available", - .ETIME = "Timer expired", - .ENOSR = "Out of streams resources", - .ENONET = "Machine is not on the network", - .ENOPKG = "Package not installed", - .EREMOTE = "Object is remote", - .ENOLINK = "Link has been severed", - .EADV = "Advertise error", - .ESRMNT = "Srmount error", - .ECOMM = "Communication error on send", - .EPROTO = "Protocol error", - .EMULTIHOP = "Multihop attempted", - .EDOTDOT = "RFS specific error", - .EBADMSG = "Bad message", - .EOVERFLOW = "Value too large for defined data type", - .ENOTUNIQ = "Name not unique on network", - .EBADFD = "File descriptor in bad state", - .EREMCHG = "Remote address changed", - .ELIBACC = "Can not access a needed shared library", - .ELIBBAD = "Accessing a corrupted shared library", - .ELIBSCN = ".lib section in a.out corrupted", - .ELIBMAX = "Attempting to link in too many shared libraries", - .ELIBEXEC = "Cannot exec a shared library directly", - .EILSEQ = "Invalid or incomplete multibyte or wide character", - .ERESTART = "Interrupted system call should be restarted", - .ESTRPIPE = "Streams pipe error", - .EUSERS = "Too many users", - .ENOTSOCK = "Socket operation on non-socket", - .EDESTADDRREQ = "Destination address required", - .EMSGSIZE = "Message too long", - .EPROTOTYPE = "Protocol wrong type for socket", - .ENOPROTOOPT = "Protocol not available", - .EPROTONOSUPPORT = "Protocol not supported", - .ESOCKTNOSUPPORT = "Socket type not supported", - .EOPNOTSUPP = "Operation not supported", - .EPFNOSUPPORT = "Protocol family not supported", - .EAFNOSUPPORT = "Address family not supported by protocol", - .EADDRINUSE = "Address already in use", - .EADDRNOTAVAIL = "Cannot assign requested address", - .ENETDOWN = "Network is down", - .ENETUNREACH = "Network is unreachable", - .ENETRESET = "Network dropped connection on reset", - .ECONNABORTED = "Software caused connection abort", - .ECONNRESET = "Connection reset by peer", - .ENOBUFS = "No buffer space available", - .EISCONN = "Transport endpoint is already connected", - .ENOTCONN = "Transport endpoint is not connected", - .ESHUTDOWN = "Cannot send after transport endpoint shutdown", - .ETOOMANYREFS = "Too many references: cannot splice", - .ETIMEDOUT = "Connection timed out", - .ECONNREFUSED = "Connection refused", - .EHOSTDOWN = "Host is down", - .EHOSTUNREACH = "No route to host", - .EALREADY = "Operation already in progress", - .EINPROGRESS = "Operation now in progress", - .ESTALE = "Stale file handle", - .EUCLEAN = "Structure needs cleaning", - .ENOTNAM = "Not a XENIX named type file", - .ENAVAIL = "No XENIX semaphores available", - .EISNAM = "Is a named type file", - .EREMOTEIO = "Remote I/O error", - .EDQUOT = "Disk quota exceeded", - .ENOMEDIUM = "No medium found", - .EMEDIUMTYPE = "Wrong medium type", - .ECANCELED = "Operation canceled", - .ENOKEY = "Required key not available", - .EKEYEXPIRED = "Key has expired", - .EKEYREVOKED = "Key has been revoked", - .EKEYREJECTED = "Key was rejected by service", - .EOWNERDEAD = "Owner died", - .ENOTRECOVERABLE = "State not recoverable", - .ERFKILL = "Operation not possible due to RF-kill", - .EHWPOISON = "Memory page has hardware error", - } - if Platform_Error.NONE <= e && e <= max(Platform_Error) { - return pe_strings[e] - } - } - return "" -} - -@(private, require_results) +// Attempts to convert an `Error` `ferr` into an `io.Error` +@(private) error_to_io_error :: proc(ferr: Error) -> io.Error { if ferr == nil { return .None diff --git a/core/os/os2/errors_js.odin b/core/os/errors_js.odin similarity index 100% rename from core/os/os2/errors_js.odin rename to core/os/errors_js.odin diff --git a/core/os/os2/errors_linux.odin b/core/os/errors_linux.odin similarity index 100% rename from core/os/os2/errors_linux.odin rename to core/os/errors_linux.odin diff --git a/core/os/os2/errors_posix.odin b/core/os/errors_posix.odin similarity index 100% rename from core/os/os2/errors_posix.odin rename to core/os/errors_posix.odin diff --git a/core/os/os2/errors_wasi.odin b/core/os/errors_wasi.odin similarity index 100% rename from core/os/os2/errors_wasi.odin rename to core/os/errors_wasi.odin diff --git a/core/os/os2/errors_windows.odin b/core/os/errors_windows.odin similarity index 100% rename from core/os/os2/errors_windows.odin rename to core/os/errors_windows.odin diff --git a/core/os/os2/file.odin b/core/os/file.odin similarity index 100% rename from core/os/os2/file.odin rename to core/os/file.odin diff --git a/core/os/os2/file_js.odin b/core/os/file_js.odin similarity index 100% rename from core/os/os2/file_js.odin rename to core/os/file_js.odin diff --git a/core/os/os2/file_linux.odin b/core/os/file_linux.odin similarity index 100% rename from core/os/os2/file_linux.odin rename to core/os/file_linux.odin diff --git a/core/os/os2/file_posix.odin b/core/os/file_posix.odin similarity index 100% rename from core/os/os2/file_posix.odin rename to core/os/file_posix.odin diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/file_posix_darwin.odin similarity index 100% rename from core/os/os2/file_posix_darwin.odin rename to core/os/file_posix_darwin.odin diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/file_posix_freebsd.odin similarity index 100% rename from core/os/os2/file_posix_freebsd.odin rename to core/os/file_posix_freebsd.odin diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/file_posix_netbsd.odin similarity index 100% rename from core/os/os2/file_posix_netbsd.odin rename to core/os/file_posix_netbsd.odin diff --git a/core/os/os2/file_posix_other.odin b/core/os/file_posix_other.odin similarity index 100% rename from core/os/os2/file_posix_other.odin rename to core/os/file_posix_other.odin diff --git a/core/os/os2/file_stream.odin b/core/os/file_stream.odin similarity index 100% rename from core/os/os2/file_stream.odin rename to core/os/file_stream.odin diff --git a/core/os/os2/file_util.odin b/core/os/file_util.odin similarity index 100% rename from core/os/os2/file_util.odin rename to core/os/file_util.odin diff --git a/core/os/os2/file_wasi.odin b/core/os/file_wasi.odin similarity index 100% rename from core/os/os2/file_wasi.odin rename to core/os/file_wasi.odin diff --git a/core/os/os2/file_windows.odin b/core/os/file_windows.odin similarity index 100% rename from core/os/os2/file_windows.odin rename to core/os/file_windows.odin diff --git a/core/os/os2/heap.odin b/core/os/heap.odin similarity index 100% rename from core/os/os2/heap.odin rename to core/os/heap.odin diff --git a/core/os/os2/heap_js.odin b/core/os/heap_js.odin similarity index 100% rename from core/os/os2/heap_js.odin rename to core/os/heap_js.odin diff --git a/core/os/os2/heap_linux.odin b/core/os/heap_linux.odin similarity index 100% rename from core/os/os2/heap_linux.odin rename to core/os/heap_linux.odin diff --git a/core/os/os2/heap_posix.odin b/core/os/heap_posix.odin similarity index 100% rename from core/os/os2/heap_posix.odin rename to core/os/heap_posix.odin diff --git a/core/os/os2/heap_wasi.odin b/core/os/heap_wasi.odin similarity index 100% rename from core/os/os2/heap_wasi.odin rename to core/os/heap_wasi.odin diff --git a/core/os/os2/heap_windows.odin b/core/os/heap_windows.odin similarity index 100% rename from core/os/os2/heap_windows.odin rename to core/os/heap_windows.odin diff --git a/core/os/os2/internal_util.odin b/core/os/internal_util.odin similarity index 100% rename from core/os/os2/internal_util.odin rename to core/os/internal_util.odin diff --git a/core/os/dir_unix.odin b/core/os/old/dir_unix.odin similarity index 100% rename from core/os/dir_unix.odin rename to core/os/old/dir_unix.odin diff --git a/core/os/old/dir_windows.odin b/core/os/old/dir_windows.odin new file mode 100644 index 000000000..40f4b9e9b --- /dev/null +++ b/core/os/old/dir_windows.odin @@ -0,0 +1,114 @@ +package os + +import win32 "core:sys/windows" +import "core:strings" +import "base:runtime" + +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + @(require_results) + find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:]) or_else ""}) + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + fi.mode |= 0o444 + } else { + fi.mode |= 0o666 + } + + is_sym := false + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { + is_sym = false + } else { + is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT + } + + if is_sym { + fi.mode |= File_Mode_Sym_Link + } else { + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + fi.mode |= 0o111 | File_Mode_Dir + } + + // fi.mode |= file_type_mode(h); + } + + windows_set_file_info_times(&fi, d) + + fi.is_dir = fi.mode & File_Mode_Dir != 0 + return + } + + if fd == 0 { + return nil, ERROR_INVALID_HANDLE + } + + context.allocator = allocator + + h := win32.HANDLE(fd) + + dir_fi, _ := file_info_from_get_file_information_by_handle("", h) + if !dir_fi.is_dir { + return nil, .Not_Dir + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + if len(wpath) == 0 { + return + } + + dfi := make([dynamic]File_Info, 0, size) or_return + + wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return + copy(wpath_search, wpath) + wpath_search[len(wpath)+0] = '\\' + wpath_search[len(wpath)+1] = '*' + wpath_search[len(wpath)+2] = 0 + + path := cleanpath_from_buf(wpath) + defer delete(path) + + find_data := &win32.WIN32_FIND_DATAW{} + find_handle := win32.FindFirstFileW(cstring16(raw_data(wpath_search)), find_data) + if find_handle == win32.INVALID_HANDLE_VALUE { + err = get_last_error() + return dfi[:], err + } + defer win32.FindClose(find_handle) + for n != 0 { + fi: File_Info + fi = find_data_to_file_info(path, find_data) + if fi.name != "" { + append(&dfi, fi) + n -= 1 + } + + if !win32.FindNextFileW(find_handle, find_data) { + e := get_last_error() + if e == ERROR_NO_MORE_FILES { + break + } + return dfi[:], e + } + } + + return dfi[:], nil +} diff --git a/core/os/old/env_windows.odin b/core/os/old/env_windows.odin new file mode 100644 index 000000000..ef658b0a1 --- /dev/null +++ b/core/os/old/env_windows.odin @@ -0,0 +1,140 @@ +package os + +import win32 "core:sys/windows" +import "base:runtime" + +// lookup_env gets the value of the environment variable named by the key +// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true +// Otherwise the returned value will be empty and the boolean will be false +// NOTE: the value will be allocated with the supplied allocator +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + if key == "" { + return + } + wkey := win32.utf8_to_wstring(key) + n := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + b, _ := make([dynamic]u16, n, context.temp_allocator) + n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false + } + value, _ = win32.utf16_to_utf8(b[:n], allocator) + found = true + return +} + +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + key_buf: [513]u16 + wkey := win32.utf8_to_wstring(key_buf[:], key) + if wkey == nil { + return "", .Buffer_Full + } + + n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n2 == 0 { + return "", .Env_Var_Not_Found + } + + val_buf: [513]u16 + n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) + if n2 == 0 { + return "", .Env_Var_Not_Found + } else if int(n2) > len(buf) { + return "", .Buffer_Full + } + + value = win32.utf16_to_utf8(buf, val_buf[:n2]) + + return value, nil +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +// get_env retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: the value will be allocated with the supplied allocator +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + + +// set_env sets the value of the environment variable named by the key +set_env :: proc(key, value: string) -> Error { + k := win32.utf8_to_wstring(key) + v := win32.utf8_to_wstring(value) + + if !win32.SetEnvironmentVariableW(k, v) { + return get_last_error() + } + return nil +} + +// unset_env unsets a single environment variable +unset_env :: proc(key: string) -> Error { + k := win32.utf8_to_wstring(key) + if !win32.SetEnvironmentVariableW(k, nil) { + return get_last_error() + } + return nil +} + +// environ returns a copy of strings representing the environment, in the form "key=value" +// NOTE: the slice of strings and the strings with be allocated using the supplied allocator +@(require_results) +environ :: proc(allocator := context.allocator) -> []string { + envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) + if envs == nil { + return nil + } + defer win32.FreeEnvironmentStringsW(envs) + + r, err := make([dynamic]string, 0, 50, allocator) + if err != nil { + return nil + } + for from, i := 0, 0; true; i += 1 { + if c := envs[i]; c == 0 { + if i <= from { + break + } + append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "") + from = i + 1 + } + } + + return r[:] +} + + +// clear_env deletes all environment variables +clear_env :: proc() { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + envs := environ(context.temp_allocator) + for env in envs { + for j in 1.. string where intrinsics.type_is_enum(Platform_Error) { + if e == nil { + return "" + } + + when ODIN_OS == .Darwin { + if s := string(_darwin_string_error(i32(e))); s != "" { + return s + } + } + + when ODIN_OS != .Linux { + @(require_results) + binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check { + n := len(array) + left, right := 0, n + for left < right { + mid := int(uint(left+right) >> 1) + if array[mid] < key { + left = mid+1 + } else { + // equal or greater + right = mid + } + } + return left, left < n && array[left] == key + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := binary_search(ti.values, err); ok { + return ti.names[idx] + } + } else { + @(rodata, static) + pe_strings := [Platform_Error]string{ + .NONE = "", + .EPERM = "Operation not permitted", + .ENOENT = "No such file or directory", + .ESRCH = "No such process", + .EINTR = "Interrupted system call", + .EIO = "Input/output error", + .ENXIO = "No such device or address", + .E2BIG = "Argument list too long", + .ENOEXEC = "Exec format error", + .EBADF = "Bad file descriptor", + .ECHILD = "No child processes", + .EAGAIN = "Resource temporarily unavailable", + .ENOMEM = "Cannot allocate memory", + .EACCES = "Permission denied", + .EFAULT = "Bad address", + .ENOTBLK = "Block device required", + .EBUSY = "Device or resource busy", + .EEXIST = "File exists", + .EXDEV = "Invalid cross-device link", + .ENODEV = "No such device", + .ENOTDIR = "Not a directory", + .EISDIR = "Is a directory", + .EINVAL = "Invalid argument", + .ENFILE = "Too many open files in system", + .EMFILE = "Too many open files", + .ENOTTY = "Inappropriate ioctl for device", + .ETXTBSY = "Text file busy", + .EFBIG = "File too large", + .ENOSPC = "No space left on device", + .ESPIPE = "Illegal seek", + .EROFS = "Read-only file system", + .EMLINK = "Too many links", + .EPIPE = "Broken pipe", + .EDOM = "Numerical argument out of domain", + .ERANGE = "Numerical result out of range", + .EDEADLK = "Resource deadlock avoided", + .ENAMETOOLONG = "File name too long", + .ENOLCK = "No locks available", + .ENOSYS = "Function not implemented", + .ENOTEMPTY = "Directory not empty", + .ELOOP = "Too many levels of symbolic links", + .EUNKNOWN_41 = "Unknown Error (41)", + .ENOMSG = "No message of desired type", + .EIDRM = "Identifier removed", + .ECHRNG = "Channel number out of range", + .EL2NSYNC = "Level 2 not synchronized", + .EL3HLT = "Level 3 halted", + .EL3RST = "Level 3 reset", + .ELNRNG = "Link number out of range", + .EUNATCH = "Protocol driver not attached", + .ENOCSI = "No CSI structure available", + .EL2HLT = "Level 2 halted", + .EBADE = "Invalid exchange", + .EBADR = "Invalid request descriptor", + .EXFULL = "Exchange full", + .ENOANO = "No anode", + .EBADRQC = "Invalid request code", + .EBADSLT = "Invalid slot", + .EUNKNOWN_58 = "Unknown Error (58)", + .EBFONT = "Bad font file format", + .ENOSTR = "Device not a stream", + .ENODATA = "No data available", + .ETIME = "Timer expired", + .ENOSR = "Out of streams resources", + .ENONET = "Machine is not on the network", + .ENOPKG = "Package not installed", + .EREMOTE = "Object is remote", + .ENOLINK = "Link has been severed", + .EADV = "Advertise error", + .ESRMNT = "Srmount error", + .ECOMM = "Communication error on send", + .EPROTO = "Protocol error", + .EMULTIHOP = "Multihop attempted", + .EDOTDOT = "RFS specific error", + .EBADMSG = "Bad message", + .EOVERFLOW = "Value too large for defined data type", + .ENOTUNIQ = "Name not unique on network", + .EBADFD = "File descriptor in bad state", + .EREMCHG = "Remote address changed", + .ELIBACC = "Can not access a needed shared library", + .ELIBBAD = "Accessing a corrupted shared library", + .ELIBSCN = ".lib section in a.out corrupted", + .ELIBMAX = "Attempting to link in too many shared libraries", + .ELIBEXEC = "Cannot exec a shared library directly", + .EILSEQ = "Invalid or incomplete multibyte or wide character", + .ERESTART = "Interrupted system call should be restarted", + .ESTRPIPE = "Streams pipe error", + .EUSERS = "Too many users", + .ENOTSOCK = "Socket operation on non-socket", + .EDESTADDRREQ = "Destination address required", + .EMSGSIZE = "Message too long", + .EPROTOTYPE = "Protocol wrong type for socket", + .ENOPROTOOPT = "Protocol not available", + .EPROTONOSUPPORT = "Protocol not supported", + .ESOCKTNOSUPPORT = "Socket type not supported", + .EOPNOTSUPP = "Operation not supported", + .EPFNOSUPPORT = "Protocol family not supported", + .EAFNOSUPPORT = "Address family not supported by protocol", + .EADDRINUSE = "Address already in use", + .EADDRNOTAVAIL = "Cannot assign requested address", + .ENETDOWN = "Network is down", + .ENETUNREACH = "Network is unreachable", + .ENETRESET = "Network dropped connection on reset", + .ECONNABORTED = "Software caused connection abort", + .ECONNRESET = "Connection reset by peer", + .ENOBUFS = "No buffer space available", + .EISCONN = "Transport endpoint is already connected", + .ENOTCONN = "Transport endpoint is not connected", + .ESHUTDOWN = "Cannot send after transport endpoint shutdown", + .ETOOMANYREFS = "Too many references: cannot splice", + .ETIMEDOUT = "Connection timed out", + .ECONNREFUSED = "Connection refused", + .EHOSTDOWN = "Host is down", + .EHOSTUNREACH = "No route to host", + .EALREADY = "Operation already in progress", + .EINPROGRESS = "Operation now in progress", + .ESTALE = "Stale file handle", + .EUCLEAN = "Structure needs cleaning", + .ENOTNAM = "Not a XENIX named type file", + .ENAVAIL = "No XENIX semaphores available", + .EISNAM = "Is a named type file", + .EREMOTEIO = "Remote I/O error", + .EDQUOT = "Disk quota exceeded", + .ENOMEDIUM = "No medium found", + .EMEDIUMTYPE = "Wrong medium type", + .ECANCELED = "Operation canceled", + .ENOKEY = "Required key not available", + .EKEYEXPIRED = "Key has expired", + .EKEYREVOKED = "Key has been revoked", + .EKEYREJECTED = "Key was rejected by service", + .EOWNERDEAD = "Owner died", + .ENOTRECOVERABLE = "State not recoverable", + .ERFKILL = "Operation not possible due to RF-kill", + .EHWPOISON = "Memory page has hardware error", + } + if Platform_Error.NONE <= e && e <= max(Platform_Error) { + return pe_strings[e] + } + } + return "" +} + +@(private, require_results) +error_to_io_error :: proc(ferr: Error) -> io.Error { + if ferr == nil { + return .None + } + return ferr.(io.Error) or_else .Unknown +} diff --git a/core/os/os.odin b/core/os/old/os.odin similarity index 100% rename from core/os/os.odin rename to core/os/old/os.odin diff --git a/core/os/os_darwin.odin b/core/os/old/os_darwin.odin similarity index 100% rename from core/os/os_darwin.odin rename to core/os/old/os_darwin.odin diff --git a/core/os/os_essence.odin b/core/os/old/os_essence.odin similarity index 100% rename from core/os/os_essence.odin rename to core/os/old/os_essence.odin diff --git a/core/os/os_freebsd.odin b/core/os/old/os_freebsd.odin similarity index 100% rename from core/os/os_freebsd.odin rename to core/os/old/os_freebsd.odin diff --git a/core/os/os_freestanding.odin b/core/os/old/os_freestanding.odin similarity index 100% rename from core/os/os_freestanding.odin rename to core/os/old/os_freestanding.odin diff --git a/core/os/os_haiku.odin b/core/os/old/os_haiku.odin similarity index 100% rename from core/os/os_haiku.odin rename to core/os/old/os_haiku.odin diff --git a/core/os/os_js.odin b/core/os/old/os_js.odin similarity index 100% rename from core/os/os_js.odin rename to core/os/old/os_js.odin diff --git a/core/os/os_linux.odin b/core/os/old/os_linux.odin similarity index 100% rename from core/os/os_linux.odin rename to core/os/old/os_linux.odin diff --git a/core/os/os_netbsd.odin b/core/os/old/os_netbsd.odin similarity index 100% rename from core/os/os_netbsd.odin rename to core/os/old/os_netbsd.odin diff --git a/core/os/os_openbsd.odin b/core/os/old/os_openbsd.odin similarity index 100% rename from core/os/os_openbsd.odin rename to core/os/old/os_openbsd.odin diff --git a/core/os/os_wasi.odin b/core/os/old/os_wasi.odin similarity index 100% rename from core/os/os_wasi.odin rename to core/os/old/os_wasi.odin diff --git a/core/os/os_windows.odin b/core/os/old/os_windows.odin similarity index 100% rename from core/os/os_windows.odin rename to core/os/old/os_windows.odin diff --git a/core/os/old/stat.odin b/core/os/old/stat.odin new file mode 100644 index 000000000..21a4961d1 --- /dev/null +++ b/core/os/old/stat.odin @@ -0,0 +1,33 @@ +package os + +import "core:time" + +File_Info :: struct { + fullpath: string, // allocated + name: string, // uses `fullpath` as underlying data + size: i64, + mode: File_Mode, + is_dir: bool, + creation_time: time.Time, + modification_time: time.Time, + access_time: time.Time, +} + +file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { + for i := len(infos)-1; i >= 0; i -= 1 { + file_info_delete(infos[i], allocator) + } + delete(infos, allocator) +} + +file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { + delete(fi.fullpath, allocator) +} + +File_Mode :: distinct u32 + +File_Mode_Dir :: File_Mode(1<<16) +File_Mode_Named_Pipe :: File_Mode(1<<17) +File_Mode_Device :: File_Mode(1<<18) +File_Mode_Char_Device :: File_Mode(1<<19) +File_Mode_Sym_Link :: File_Mode(1<<20) diff --git a/core/os/stat_unix.odin b/core/os/old/stat_unix.odin similarity index 100% rename from core/os/stat_unix.odin rename to core/os/old/stat_unix.odin diff --git a/core/os/old/stat_windows.odin b/core/os/old/stat_windows.odin new file mode 100644 index 000000000..662c9f9e6 --- /dev/null +++ b/core/os/old/stat_windows.odin @@ -0,0 +1,303 @@ +package os + +import "core:time" +import "base:runtime" +import win32 "core:sys/windows" + +@(private, require_results) +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) { + context.allocator = allocator + + name := name + if name == "" { + name = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + p := win32.utf8_to_utf16(name, context.temp_allocator) + buf := make([dynamic]u16, 100) + defer delete(buf) + for { + n := win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) + if n == 0 { + return "", get_last_error() + } + if n <= u32(len(buf)) { + return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil + } + resize(&buf, len(buf)*2) + } + + return +} + +@(private, require_results) +_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) { + if len(name) == 0 { + return {}, ERROR_PATH_NOT_FOUND + } + + context.allocator = allocator + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator) + fa: win32.WIN32_FILE_ATTRIBUTE_DATA + ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) + if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + // Not a symlink + return file_info_from_win32_file_attribute_data(&fa, name) + } + + err := 0 if ok else win32.GetLastError() + + if err == win32.ERROR_SHARING_VIOLATION { + fd: win32.WIN32_FIND_DATAW + sh := win32.FindFirstFileW(wname, &fd) + if sh == win32.INVALID_HANDLE_VALUE { + e = get_last_error() + return + } + win32.FindClose(sh) + + return file_info_from_win32_find_data(&fd, name) + } + + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) + if h == win32.INVALID_HANDLE_VALUE { + e = get_last_error() + return + } + defer win32.CloseHandle(h) + return file_info_from_get_file_information_by_handle(name, h) +} + + +@(require_results) +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { + attrs := win32.FILE_FLAG_BACKUP_SEMANTICS + attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT + return _stat(name, attrs, allocator) +} + +@(require_results) +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { + attrs := win32.FILE_FLAG_BACKUP_SEMANTICS + return _stat(name, attrs, allocator) +} + +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { + if fd == 0 { + err = ERROR_INVALID_HANDLE + } + context.allocator = allocator + + path := cleanpath_from_handle(fd) or_return + defer if err != nil { + delete(path) + } + + h := win32.HANDLE(fd) + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: + fi.name = basename(path) + fi.mode |= file_type_mode(h) + err = nil + case: + fi = file_info_from_get_file_information_by_handle(path, h) or_return + } + fi.fullpath = path + return +} + + +@(private, require_results) +cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { + buf := buf + N := 0 + for c, i in buf { + if c == 0 { break } + N = i+1 + } + buf = buf[:N] + + if len(buf) >= 4 && buf[0] == '\\' && buf[1] == '\\' && buf[2] == '?' && buf[3] == '\\' { + buf = buf[4:] + + /* + NOTE(Jeroen): Properly handle UNC paths. + We need to turn `\\?\UNC\synology.local` into `\\synology.local`. + */ + if len(buf) >= 3 && buf[0] == 'U' && buf[1] == 'N' && buf[2] == 'C' { + buf = buf[2:] + buf[0] = '\\' + } + } + return buf +} + +@(private, require_results) +cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + return win32.utf16_to_utf8(buf, context.allocator) +} +@(private, require_results) +cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) { + if fd == 0 { + return nil, ERROR_INVALID_HANDLE + } + h := win32.HANDLE(fd) + + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return nil, get_last_error() + } + buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) + buf_len := win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), n, 0) + return buf[:buf_len], nil +} +@(private, require_results) +cleanpath_from_buf :: proc(buf: []u16) -> string { + buf := buf + buf = cleanpath_strip_prefix(buf) + return win32.utf16_to_utf8(buf, context.allocator) or_else "" +} + +@(private, require_results) +basename :: proc(name: string) -> (base: string) { + name := name + if len(name) > 3 && name[:3] == `\\?` { + name = name[3:] + } + + if len(name) == 2 && name[1] == ':' { + return "." + } else if len(name) > 2 && name[1] == ':' { + name = name[2:] + } + i := len(name)-1 + + for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 { + name = name[:i] + } + for i -= 1; i >= 0; i -= 1 { + if name[i] == '/' || name[i] == '\\' { + name = name[i+1:] + break + } + } + return name +} + +@(private, require_results) +file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE: + return File_Mode_Named_Pipe + case win32.FILE_TYPE_CHAR: + return File_Mode_Device | File_Mode_Char_Device + } + return 0 +} + + +@(private, require_results) +file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { + if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + mode |= 0o444 + } else { + mode |= 0o666 + } + + is_sym := false + if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + is_sym = false + } else { + is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT + } + + if is_sym { + mode |= File_Mode_Sym_Link + } else { + if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + mode |= 0o111 | File_Mode_Dir + } + + if h != nil { + mode |= file_type_mode(h) + } + } + + return +} + +@(private) +windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) { + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) +} + +@(private, require_results) +file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.is_dir = fi.mode & File_Mode_Dir != 0 + + windows_set_file_info_times(&fi, d) + + fi.fullpath, e = full_path_from_name(name) + fi.name = basename(fi.fullpath) + + return +} + +@(private, require_results) +file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.is_dir = fi.mode & File_Mode_Dir != 0 + + windows_set_file_info_times(&fi, d) + + fi.fullpath, e = full_path_from_name(name) + fi.name = basename(fi.fullpath) + + return +} + +@(private, require_results) +file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) { + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(h, &d) { + err := get_last_error() + return {}, err + + } + + ti: win32.FILE_ATTRIBUTE_TAG_INFO + if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { + err := get_last_error() + if err != ERROR_INVALID_PARAMETER { + return {}, err + } + // Indicate this is a symlink on FAT file systems + ti.ReparseTag = 0 + } + + fi: File_Info + + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.mode |= file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) + fi.is_dir = fi.mode & File_Mode_Dir != 0 + + windows_set_file_info_times(&fi, &d) + + return fi, nil +} diff --git a/core/os/stream.odin b/core/os/old/stream.odin similarity index 100% rename from core/os/stream.odin rename to core/os/old/stream.odin diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin deleted file mode 100644 index a4dadca75..000000000 --- a/core/os/os2/dir_windows.odin +++ /dev/null @@ -1,144 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import "core:time" -import win32 "core:sys/windows" - -@(private="file") -find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - // Ignore "." and ".." - if d.cFileName[0] == '.' && d.cFileName[1] == 0 { - return - } - if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return - - handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0) - defer win32.CloseHandle(handle) - - fi.fullpath = path - fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0) - - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - - if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) { - #assert(size_of(fi.inode) == size_of(file_id_info.FileId)) - #assert(size_of(fi.inode) == 16) - runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16) - } - - return -} - -Read_Directory_Iterator_Impl :: struct { - find_data: win32.WIN32_FIND_DATAW, - find_handle: win32.HANDLE, - path: string, - prev_fi: File_Info, - no_more_files: bool, -} - - -@(require_results) -_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - for !it.impl.no_more_files { - err: Error - file_info_delete(it.impl.prev_fi, file_allocator()) - it.impl.prev_fi = {} - - fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) - if err != nil { - read_directory_iterator_set_error(it, it.impl.path, err) - return - } - - if fi.name != "" { - it.impl.prev_fi = fi - ok = true - index = it.index - it.index += 1 - } - - if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { - e := _get_platform_error() - if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) { - read_directory_iterator_set_error(it, it.impl.path, e) - } - it.impl.no_more_files = true - } - if ok { - return - } - } - return -} - -_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { - it.impl.no_more_files = false - - if f == nil || f.impl == nil { - read_directory_iterator_set_error(it, "", .Invalid_File) - return - } - - it.f = f - impl := (^File_Impl)(f.impl) - - // NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx. - if it.impl.find_handle != nil { - win32.FindClose(it.impl.find_handle) - } - if it.impl.path != "" { - delete(it.impl.path, file_allocator()) - } - - if !is_directory(impl.name) { - read_directory_iterator_set_error(it, impl.name, .Invalid_Dir) - return - } - - wpath := string16(impl.wname) - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - wpath_search := make([]u16, len(wpath)+3, temp_allocator) - copy(wpath_search, wpath) - wpath_search[len(wpath)+0] = '\\' - wpath_search[len(wpath)+1] = '*' - wpath_search[len(wpath)+2] = 0 - - it.impl.find_handle = win32.FindFirstFileW(cstring16(raw_data(wpath_search)), &it.impl.find_data) - if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { - read_directory_iterator_set_error(it, impl.name, _get_platform_error()) - return - } - defer if it.err.err != nil { - win32.FindClose(it.impl.find_handle) - } - - err: Error - it.impl.path, err = _cleanpath_from_buf(wpath, file_allocator()) - if err != nil { - read_directory_iterator_set_error(it, impl.name, err) - } - - return -} - -_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { - if it.f == nil { - return - } - file_info_delete(it.impl.prev_fi, file_allocator()) - delete(it.impl.path, file_allocator()) - win32.FindClose(it.impl.find_handle) -} diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin deleted file mode 100644 index d389f8860..000000000 --- a/core/os/os2/env_windows.odin +++ /dev/null @@ -1,142 +0,0 @@ -#+private -package os2 - -import win32 "core:sys/windows" -import "base:runtime" - -_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if key == "" { - return - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - wkey, _ := win32_utf8_to_wstring(key, temp_allocator) - - n := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n == 0 { - err := win32.GetLastError() - if err == win32.ERROR_ENVVAR_NOT_FOUND { - return "", false - } - return "", true - } - - b := make([]u16, n+1, temp_allocator) - - n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == win32.ERROR_ENVVAR_NOT_FOUND { - return "", false - } - return "", false - } - - value = win32_utf16_to_utf8(string16(b[:n]), allocator) or_else "" - found = true - return -} - -// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. -// Note that it is limited to environment names and values of 512 utf-16 values each -// due to the necessary utf-8 <> utf-16 conversion. -@(require_results) -_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - key_buf: [513]u16 - wkey := win32.utf8_to_wstring(key_buf[:], key) - if wkey == nil { - return "", .Buffer_Full - } - - n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n2 == 0 { - return "", .Env_Var_Not_Found - } - - val_buf: [513]u16 - n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) - if n2 == 0 { - return "", .Env_Var_Not_Found - } else if int(n2) > len(buf) { - return "", .Buffer_Full - } - - value = win32.utf16_to_utf8(buf, val_buf[:n2]) - - return value, nil -} -_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - -_set_env :: proc(key, value: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - k := win32_utf8_to_wstring(key, temp_allocator) or_return - v := win32_utf8_to_wstring(value, temp_allocator) or_return - - if !win32.SetEnvironmentVariableW(k, v) { - return _get_platform_error() - } - return nil -} - -_unset_env :: proc(key: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - k, _ := win32_utf8_to_wstring(key, temp_allocator) - return bool(win32.SetEnvironmentVariableW(k, nil)) -} - -_clear_env :: proc() { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - envs, _ := environ(temp_allocator) - for env in envs { - for j in 1.. io.Error { - if ferr == nil { - return .None - } - return ferr.(io.Error) or_else .Unknown -} diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin deleted file mode 100644 index 0a9ac4e57..000000000 --- a/core/os/os2/stat.odin +++ /dev/null @@ -1,117 +0,0 @@ -package os2 - -import "base:runtime" -import "core:strings" -import "core:time" - -Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) - -/* - `File_Info` describes a file and is returned from `stat`, `fstat`, and `lstat`. -*/ -File_Info :: struct { - fullpath: string, // fullpath of the file - name: string, // base name of the file - - inode: u128, // might be zero if cannot be determined - size: i64 `fmt:"M"`, // length in bytes for regular files; system-dependent for other file types - mode: Permissions, // file permission flags - type: File_Type, - - creation_time: time.Time, - modification_time: time.Time, - access_time: time.Time, -} - -@(require_results) -file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { - cloned = fi - cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return - _, cloned.name = split_path(cloned.fullpath) - return -} - -file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { - #reverse for info in infos { - file_info_delete(info, allocator) - } - delete(infos, allocator) -} - -file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { - delete(fi.fullpath, allocator) -} - -@(require_results) -fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil { - return {}, nil - } else if f.stream.procedure != nil { - fi: File_Info - data := ([^]byte)(&fi)[:size_of(fi)] - _, err := f.stream.procedure(f, .Fstat, data, 0, nil, allocator) - return fi, err - } - return {}, .Invalid_Callback -} - -/* - `stat` returns a `File_Info` describing the named file from the file system. - The resulting `File_Info` must be deleted with `file_info_delete`. -*/ -@(require_results) -stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - return _stat(name, allocator) -} - -lstat :: stat_do_not_follow_links - -/* - Returns a `File_Info` describing the named file from the file system. - If the file is a symbolic link, the `File_Info` returns describes the symbolic link, - rather than following the link. - The resulting `File_Info` must be deleted with `file_info_delete`. -*/ -@(require_results) -stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - return _lstat(name, allocator) -} - - -/* - Returns true if two `File_Info`s are equivalent. -*/ -@(require_results) -same_file :: proc(fi1, fi2: File_Info) -> bool { - return _same_file(fi1, fi2) -} - - -last_write_time :: modification_time -last_write_time_by_name :: modification_time_by_path - -/* - Returns the modification time of the file `f`. - The resolution of the timestamp is system-dependent. -*/ -@(require_results) -modification_time :: proc(f: ^File) -> (time.Time, Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - fi, err := fstat(f, temp_allocator) - return fi.modification_time, err -} - -/* - Returns the modification time of the named file `path`. - The resolution of the timestamp is system-dependent. -*/ -@(require_results) -modification_time_by_path :: proc(path: string) -> (time.Time, Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - fi, err := stat(path, temp_allocator) - return fi.modification_time, err -} - -is_reserved_name :: proc(path: string) -> bool { - return _is_reserved_name(path) -} \ No newline at end of file diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin deleted file mode 100644 index 651029ac3..000000000 --- a/core/os/os2/stat_windows.odin +++ /dev/null @@ -1,393 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import "core:time" -import "core:strings" -import win32 "core:sys/windows" - -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if f == nil || (^File_Impl)(f.impl).fd == nil { - return - } - - path := _cleanpath_from_handle(f, allocator) or_return - - h := _handle(f) - switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi = File_Info { - fullpath = path, - name = basename(path), - type = file_type(h), - } - return - } - - return _file_info_from_get_file_information_by_handle(path, h, allocator) -} - -_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) -} - -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) -} - -_same_file :: proc(fi1, fi2: File_Info) -> bool { - return fi1.fullpath == fi2.fullpath -} - -full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { - name := name - if name == "" { - name = "." - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - p := win32_utf8_to_utf16(name, temp_allocator) or_return - - n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) - if n == 0 { - return "", _get_platform_error() - } - buf := make([]u16, n+1, temp_allocator) - n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) - if n == 0 { - return "", _get_platform_error() - } - return win32_utf16_to_utf8(buf[:n], allocator) -} - -internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { - if len(name) == 0 { - return {}, .Not_Exist - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - wname := _fix_long_path(name, temp_allocator) or_return - fa: win32.WIN32_FILE_ATTRIBUTE_DATA - ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) - if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { - // Not a symlink - fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return - if fi.type == .Undetermined { - fi.type = _file_type_from_create_file(wname, create_file_attributes) - } - return - } - - err := 0 if ok else win32.GetLastError() - - if err == win32.ERROR_SHARING_VIOLATION { - fd: win32.WIN32_FIND_DATAW - sh := win32.FindFirstFileW(wname, &fd) - if sh == win32.INVALID_HANDLE_VALUE { - e = _get_platform_error() - return - } - win32.FindClose(sh) - - fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return - if fi.type == .Undetermined { - fi.type = _file_type_from_create_file(wname, create_file_attributes) - } - return - } - - h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) - if h == win32.INVALID_HANDLE_VALUE { - e = _get_platform_error() - return - } - defer win32.CloseHandle(h) - return _file_info_from_get_file_information_by_handle(name, h, allocator) -} - -_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { - buf := buf - N := 0 - for c, i in buf { - if c == 0 { break } - N = i+1 - } - buf = buf[:N] - - if len(buf) >= 4 { - if buf[0] == '\\' && - buf[1] == '\\' && - buf[2] == '?' && - buf[3] == '\\' { - buf = buf[4:] - } - } - return buf -} - -_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { - if f == nil { - return "", nil - } - h := _handle(f) - - n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) - if n == 0 { - return "", _get_platform_error() - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := make([]u16, max(n, 260)+1, temp_allocator) - n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) - return _cleanpath_from_buf(string16(buf[:n]), allocator) -} - -_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { - if f == nil { - return nil, nil - } - h := _handle(f) - - n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) - if n == 0 { - return nil, _get_platform_error() - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - buf := make([]u16, max(n, 260)+1, temp_allocator) - n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) - return _cleanpath_strip_prefix(buf[:n]), nil -} - -_cleanpath_from_buf :: proc(buf: string16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - buf := transmute([]u16)buf - buf = _cleanpath_strip_prefix(buf) - return win32_utf16_to_utf8(buf, allocator) -} - -basename :: proc(name: string) -> (base: string) { - name := name - if len(name) > 3 && name[:3] == `\\?` { - name = name[3:] - } - - if len(name) == 2 && name[1] == ':' { - return "." - } else if len(name) > 2 && name[1] == ':' { - name = name[2:] - } - i := len(name)-1 - - for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 { - name = name[:i] - } - for i -= 1; i >= 0; i -= 1 { - if name[i] == '/' || name[i] == '\\' { - name = name[i+1:] - break - } - } - return name -} - -file_type :: proc(h: win32.HANDLE) -> File_Type { - switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE: return .Named_Pipe - case win32.FILE_TYPE_CHAR: return .Character_Device - case win32.FILE_TYPE_DISK: return .Regular - } - return .Undetermined -} - -_file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type { - h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) - if h == win32.INVALID_HANDLE_VALUE { - return .Undetermined - } - defer win32.CloseHandle(h) - return file_type(h) -} - -_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: Permissions) { - if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { - mode += Permissions_Write_All - } else { - mode += Permissions_Read_Write_All - } - - is_sym := false - if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { - is_sym = false - } else { - is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT - } - - if is_sym { - type = .Symlink - } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - type = .Directory - mode += Permissions_Execute_All - } else if h != nil { - type = file_type(h) - } - return -} - -// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) -time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) { - win := u64(t._nsec / 100) + 116444736000000000 - return win32.LARGE_INTEGER(win) -} - -filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) { - return {_nsec=(i64(ft) - 116444736000000000) * 100} -} - -filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) { - return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32) -} - -filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li} - -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.type = type - fi.mode |= mode - fi.creation_time = filetime_as_time(d.ftCreationTime) - fi.modification_time = filetime_as_time(d.ftLastWriteTime) - fi.access_time = filetime_as_time(d.ftLastAccessTime) - fi.fullpath, e = full_path_from_name(name, allocator) - fi.name = basename(fi.fullpath) - return -} - -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.type = type - fi.mode |= mode - fi.creation_time = filetime_as_time(d.ftCreationTime) - fi.modification_time = filetime_as_time(d.ftLastWriteTime) - fi.access_time = filetime_as_time(d.ftLastAccessTime) - fi.fullpath, e = full_path_from_name(name, allocator) - fi.name = basename(fi.fullpath) - return -} - -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { - d: win32.BY_HANDLE_FILE_INFORMATION - if !win32.GetFileInformationByHandle(h, &d) { - return {}, _get_platform_error() - - } - - ti: win32.FILE_ATTRIBUTE_TAG_INFO - if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { - err := _get_platform_error() - if perr, ok := is_platform_error(err); ok && perr != i32(win32.ERROR_INVALID_PARAMETER) { - return {}, err - } - // Indicate this is a symlink on FAT file systems - ti.ReparseTag = 0 - } - fi: File_Info - fi.fullpath = path - fi.name = basename(path) - fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) - fi.type = type - fi.mode |= mode - fi.creation_time = filetime_as_time(d.ftCreationTime) - fi.modification_time = filetime_as_time(d.ftLastWriteTime) - fi.access_time = filetime_as_time(d.ftLastAccessTime) - return fi, nil -} - -reserved_names := [?]string{ - "CON", "PRN", "AUX", "NUL", - "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", -} - -_is_reserved_name :: proc(path: string) -> bool { - if len(path) == 0 { - return false - } - for reserved in reserved_names { - if strings.equal_fold(path, reserved) { - return true - } - } - return false -} - -_volume_name_len :: proc(path: string) -> (length: int) { - if len(path) < 2 { - return 0 - } - - if path[1] == ':' { - switch path[0] { - case 'a'..='z', 'A'..='Z': - return 2 - } - } - - /* - See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - Further allowed paths can be of the form of: - - \\server\share or \\server\share\more\path - - \\?\C:\... - - \\.\PhysicalDriveX - */ - // Any remaining kind of path has to start with two slashes. - if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) { - return 0 - } - - // Device path. The volume name is the whole string - if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) { - return len(path) - } - - // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share` - prefix := 2 - - // File namespace. - if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) { - if _is_path_separator(path[4]) { - // `\\?\\` UNC path in file namespace - prefix = 5 - } - - if len(path) >= 6 && path[5] == ':' { - switch path[4] { - case 'a'..='z', 'A'..='Z': - return 6 - case: - return 0 - } - } - } - - // UNC path, minimum version of the volume is `\\h\s` for host, share. - // Can also contain an IP address in the host position. - slash_count := 0 - for i in prefix.. 0 { - slash_count += 1 - - if slash_count == 2 { - return i - } - } - } - - return len(path) -} \ No newline at end of file diff --git a/core/os/os2/path.odin b/core/os/path.odin similarity index 100% rename from core/os/os2/path.odin rename to core/os/path.odin diff --git a/core/os/os2/path_darwin.odin b/core/os/path_darwin.odin similarity index 100% rename from core/os/os2/path_darwin.odin rename to core/os/path_darwin.odin diff --git a/core/os/os2/path_freebsd.odin b/core/os/path_freebsd.odin similarity index 100% rename from core/os/os2/path_freebsd.odin rename to core/os/path_freebsd.odin diff --git a/core/os/os2/path_js.odin b/core/os/path_js.odin similarity index 100% rename from core/os/os2/path_js.odin rename to core/os/path_js.odin diff --git a/core/os/os2/path_linux.odin b/core/os/path_linux.odin similarity index 100% rename from core/os/os2/path_linux.odin rename to core/os/path_linux.odin diff --git a/core/os/os2/path_netbsd.odin b/core/os/path_netbsd.odin similarity index 100% rename from core/os/os2/path_netbsd.odin rename to core/os/path_netbsd.odin diff --git a/core/os/os2/path_openbsd.odin b/core/os/path_openbsd.odin similarity index 100% rename from core/os/os2/path_openbsd.odin rename to core/os/path_openbsd.odin diff --git a/core/os/os2/path_posix.odin b/core/os/path_posix.odin similarity index 100% rename from core/os/os2/path_posix.odin rename to core/os/path_posix.odin diff --git a/core/os/os2/path_posixfs.odin b/core/os/path_posixfs.odin similarity index 100% rename from core/os/os2/path_posixfs.odin rename to core/os/path_posixfs.odin diff --git a/core/os/os2/path_wasi.odin b/core/os/path_wasi.odin similarity index 100% rename from core/os/os2/path_wasi.odin rename to core/os/path_wasi.odin diff --git a/core/os/os2/path_windows.odin b/core/os/path_windows.odin similarity index 100% rename from core/os/os2/path_windows.odin rename to core/os/path_windows.odin diff --git a/core/os/os2/pipe.odin b/core/os/pipe.odin similarity index 100% rename from core/os/os2/pipe.odin rename to core/os/pipe.odin diff --git a/core/os/os2/pipe_js.odin b/core/os/pipe_js.odin similarity index 100% rename from core/os/os2/pipe_js.odin rename to core/os/pipe_js.odin diff --git a/core/os/os2/pipe_linux.odin b/core/os/pipe_linux.odin similarity index 100% rename from core/os/os2/pipe_linux.odin rename to core/os/pipe_linux.odin diff --git a/core/os/os2/pipe_posix.odin b/core/os/pipe_posix.odin similarity index 100% rename from core/os/os2/pipe_posix.odin rename to core/os/pipe_posix.odin diff --git a/core/os/os2/pipe_wasi.odin b/core/os/pipe_wasi.odin similarity index 100% rename from core/os/os2/pipe_wasi.odin rename to core/os/pipe_wasi.odin diff --git a/core/os/os2/pipe_windows.odin b/core/os/pipe_windows.odin similarity index 100% rename from core/os/os2/pipe_windows.odin rename to core/os/pipe_windows.odin diff --git a/core/os/os2/process.odin b/core/os/process.odin similarity index 100% rename from core/os/os2/process.odin rename to core/os/process.odin diff --git a/core/os/os2/process_freebsd.odin b/core/os/process_freebsd.odin similarity index 100% rename from core/os/os2/process_freebsd.odin rename to core/os/process_freebsd.odin diff --git a/core/os/os2/process_js.odin b/core/os/process_js.odin similarity index 100% rename from core/os/os2/process_js.odin rename to core/os/process_js.odin diff --git a/core/os/os2/process_linux.odin b/core/os/process_linux.odin similarity index 100% rename from core/os/os2/process_linux.odin rename to core/os/process_linux.odin diff --git a/core/os/os2/process_netbsd.odin b/core/os/process_netbsd.odin similarity index 100% rename from core/os/os2/process_netbsd.odin rename to core/os/process_netbsd.odin diff --git a/core/os/os2/process_openbsd.odin b/core/os/process_openbsd.odin similarity index 100% rename from core/os/os2/process_openbsd.odin rename to core/os/process_openbsd.odin diff --git a/core/os/os2/process_posix.odin b/core/os/process_posix.odin similarity index 100% rename from core/os/os2/process_posix.odin rename to core/os/process_posix.odin diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/process_posix_darwin.odin similarity index 100% rename from core/os/os2/process_posix_darwin.odin rename to core/os/process_posix_darwin.odin diff --git a/core/os/os2/process_posix_other.odin b/core/os/process_posix_other.odin similarity index 100% rename from core/os/os2/process_posix_other.odin rename to core/os/process_posix_other.odin diff --git a/core/os/os2/process_wasi.odin b/core/os/process_wasi.odin similarity index 100% rename from core/os/os2/process_wasi.odin rename to core/os/process_wasi.odin diff --git a/core/os/os2/process_windows.odin b/core/os/process_windows.odin similarity index 100% rename from core/os/os2/process_windows.odin rename to core/os/process_windows.odin diff --git a/core/os/stat.odin b/core/os/stat.odin index 21a4961d1..0a9ac4e57 100644 --- a/core/os/stat.odin +++ b/core/os/stat.odin @@ -1,33 +1,117 @@ -package os +package os2 +import "base:runtime" +import "core:strings" import "core:time" +Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) + +/* + `File_Info` describes a file and is returned from `stat`, `fstat`, and `lstat`. +*/ File_Info :: struct { - fullpath: string, // allocated - name: string, // uses `fullpath` as underlying data - size: i64, - mode: File_Mode, - is_dir: bool, + fullpath: string, // fullpath of the file + name: string, // base name of the file + + inode: u128, // might be zero if cannot be determined + size: i64 `fmt:"M"`, // length in bytes for regular files; system-dependent for other file types + mode: Permissions, // file permission flags + type: File_Type, + creation_time: time.Time, modification_time: time.Time, access_time: time.Time, } -file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { - for i := len(infos)-1; i >= 0; i -= 1 { - file_info_delete(infos[i], allocator) +@(require_results) +file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { + cloned = fi + cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return + _, cloned.name = split_path(cloned.fullpath) + return +} + +file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { + #reverse for info in infos { + file_info_delete(info, allocator) } delete(infos, allocator) } -file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { +file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { delete(fi.fullpath, allocator) } -File_Mode :: distinct u32 +@(require_results) +fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { + if f == nil { + return {}, nil + } else if f.stream.procedure != nil { + fi: File_Info + data := ([^]byte)(&fi)[:size_of(fi)] + _, err := f.stream.procedure(f, .Fstat, data, 0, nil, allocator) + return fi, err + } + return {}, .Invalid_Callback +} -File_Mode_Dir :: File_Mode(1<<16) -File_Mode_Named_Pipe :: File_Mode(1<<17) -File_Mode_Device :: File_Mode(1<<18) -File_Mode_Char_Device :: File_Mode(1<<19) -File_Mode_Sym_Link :: File_Mode(1<<20) +/* + `stat` returns a `File_Info` describing the named file from the file system. + The resulting `File_Info` must be deleted with `file_info_delete`. +*/ +@(require_results) +stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return _stat(name, allocator) +} + +lstat :: stat_do_not_follow_links + +/* + Returns a `File_Info` describing the named file from the file system. + If the file is a symbolic link, the `File_Info` returns describes the symbolic link, + rather than following the link. + The resulting `File_Info` must be deleted with `file_info_delete`. +*/ +@(require_results) +stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return _lstat(name, allocator) +} + + +/* + Returns true if two `File_Info`s are equivalent. +*/ +@(require_results) +same_file :: proc(fi1, fi2: File_Info) -> bool { + return _same_file(fi1, fi2) +} + + +last_write_time :: modification_time +last_write_time_by_name :: modification_time_by_path + +/* + Returns the modification time of the file `f`. + The resolution of the timestamp is system-dependent. +*/ +@(require_results) +modification_time :: proc(f: ^File) -> (time.Time, Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := fstat(f, temp_allocator) + return fi.modification_time, err +} + +/* + Returns the modification time of the named file `path`. + The resolution of the timestamp is system-dependent. +*/ +@(require_results) +modification_time_by_path :: proc(path: string) -> (time.Time, Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) + return fi.modification_time, err +} + +is_reserved_name :: proc(path: string) -> bool { + return _is_reserved_name(path) +} \ No newline at end of file diff --git a/core/os/os2/stat_js.odin b/core/os/stat_js.odin similarity index 100% rename from core/os/os2/stat_js.odin rename to core/os/stat_js.odin diff --git a/core/os/os2/stat_linux.odin b/core/os/stat_linux.odin similarity index 100% rename from core/os/os2/stat_linux.odin rename to core/os/stat_linux.odin diff --git a/core/os/os2/stat_posix.odin b/core/os/stat_posix.odin similarity index 100% rename from core/os/os2/stat_posix.odin rename to core/os/stat_posix.odin diff --git a/core/os/os2/stat_wasi.odin b/core/os/stat_wasi.odin similarity index 100% rename from core/os/os2/stat_wasi.odin rename to core/os/stat_wasi.odin diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 662c9f9e6..651029ac3 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -1,51 +1,82 @@ -package os +#+private +package os2 -import "core:time" import "base:runtime" +import "core:time" +import "core:strings" import win32 "core:sys/windows" -@(private, require_results) -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) { - context.allocator = allocator - +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || (^File_Impl)(f.impl).fd == nil { + return + } + + path := _cleanpath_from_handle(f, allocator) or_return + + h := _handle(f) + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: + fi = File_Info { + fullpath = path, + name = basename(path), + type = file_type(h), + } + return + } + + return _file_info_from_get_file_information_by_handle(path, h, allocator) +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} + +full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { name = "." } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := win32.utf8_to_utf16(name, context.temp_allocator) - buf := make([dynamic]u16, 100) - defer delete(buf) - for { - n := win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) - if n == 0 { - return "", get_last_error() - } - if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil - } - resize(&buf, len(buf)*2) - } - return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + p := win32_utf8_to_utf16(name, temp_allocator) or_return + + n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) + if n == 0 { + return "", _get_platform_error() + } + buf := make([]u16, n+1, temp_allocator) + n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) + if n == 0 { + return "", _get_platform_error() + } + return win32_utf16_to_utf8(buf[:n], allocator) } -@(private, require_results) -_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { - return {}, ERROR_PATH_NOT_FOUND + return {}, .Not_Exist } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - context.allocator = allocator - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - - wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator) + wname := _fix_long_path(name, temp_allocator) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink - return file_info_from_win32_file_attribute_data(&fa, name) + fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } err := 0 if ok else win32.GetLastError() @@ -54,65 +85,28 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = get_last_error() + e = _get_platform_error() return } win32.FindClose(sh) - return file_info_from_win32_find_data(&fd, name) + fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = get_last_error() + e = _get_platform_error() return } defer win32.CloseHandle(h) - return file_info_from_get_file_information_by_handle(name, h) + return _file_info_from_get_file_information_by_handle(name, h, allocator) } - -@(require_results) -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { - attrs := win32.FILE_FLAG_BACKUP_SEMANTICS - attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT - return _stat(name, attrs, allocator) -} - -@(require_results) -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { - attrs := win32.FILE_FLAG_BACKUP_SEMANTICS - return _stat(name, attrs, allocator) -} - -@(require_results) -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - if fd == 0 { - err = ERROR_INVALID_HANDLE - } - context.allocator = allocator - - path := cleanpath_from_handle(fd) or_return - defer if err != nil { - delete(path) - } - - h := win32.HANDLE(fd) - switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi.name = basename(path) - fi.mode |= file_type_mode(h) - err = nil - case: - fi = file_info_from_get_file_information_by_handle(path, h) or_return - } - fi.fullpath = path - return -} - - -@(private, require_results) -cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { +_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 for c, i in buf { @@ -121,50 +115,59 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } buf = buf[:N] - if len(buf) >= 4 && buf[0] == '\\' && buf[1] == '\\' && buf[2] == '?' && buf[3] == '\\' { - buf = buf[4:] - - /* - NOTE(Jeroen): Properly handle UNC paths. - We need to turn `\\?\UNC\synology.local` into `\\synology.local`. - */ - if len(buf) >= 3 && buf[0] == 'U' && buf[1] == 'N' && buf[2] == 'C' { - buf = buf[2:] - buf[0] = '\\' + if len(buf) >= 4 { + if buf[0] == '\\' && + buf[1] == '\\' && + buf[2] == '?' && + buf[3] == '\\' { + buf = buf[4:] } } return buf } -@(private, require_results) -cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return - return win32.utf16_to_utf8(buf, context.allocator) -} -@(private, require_results) -cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) { - if fd == 0 { - return nil, ERROR_INVALID_HANDLE +_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { + if f == nil { + return "", nil } - h := win32.HANDLE(fd) + h := _handle(f) n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) if n == 0 { - return nil, get_last_error() + return "", _get_platform_error() } - buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) - buf_len := win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), n, 0) - return buf[:buf_len], nil -} -@(private, require_results) -cleanpath_from_buf :: proc(buf: []u16) -> string { - buf := buf - buf = cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, context.allocator) or_else "" + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := make([]u16, max(n, 260)+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) + return _cleanpath_from_buf(string16(buf[:n]), allocator) +} + +_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { + if f == nil { + return nil, nil + } + h := _handle(f) + + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return nil, _get_platform_error() + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + buf := make([]u16, max(n, 260)+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) + return _cleanpath_strip_prefix(buf[:n]), nil +} + +_cleanpath_from_buf :: proc(buf: string16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + buf := transmute([]u16)buf + buf = _cleanpath_strip_prefix(buf) + return win32_utf16_to_utf8(buf, allocator) } -@(private, require_results) basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -190,114 +193,201 @@ basename :: proc(name: string) -> (base: string) { return name } -@(private, require_results) -file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { +file_type :: proc(h: win32.HANDLE) -> File_Type { switch win32.GetFileType(h) { - case win32.FILE_TYPE_PIPE: - return File_Mode_Named_Pipe - case win32.FILE_TYPE_CHAR: - return File_Mode_Device | File_Mode_Char_Device + case win32.FILE_TYPE_PIPE: return .Named_Pipe + case win32.FILE_TYPE_CHAR: return .Character_Device + case win32.FILE_TYPE_DISK: return .Regular } - return 0 + return .Undetermined } +_file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type { + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) + if h == win32.INVALID_HANDLE_VALUE { + return .Undetermined + } + defer win32.CloseHandle(h) + return file_type(h) +} -@(private, require_results) -file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { - if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { - mode |= 0o444 +_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: Permissions) { + if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + mode += Permissions_Write_All } else { - mode |= 0o666 + mode += Permissions_Read_Write_All } is_sym := false - if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { is_sym = false } else { is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT } if is_sym { - mode |= File_Mode_Sym_Link - } else { - if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - mode |= 0o111 | File_Mode_Dir - } - - if h != nil { - mode |= file_type_mode(h) - } + type = .Symlink + } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + type = .Directory + mode += Permissions_Execute_All + } else if h != nil { + type = file_type(h) } - return } -@(private) -windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) { - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) +// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) +time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) { + win := u64(t._nsec / 100) + 116444736000000000 + return win32.LARGE_INTEGER(win) } -@(private, require_results) -file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) { +filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) { + return {_nsec=(i64(ft) - 116444736000000000) * 100} +} + +filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) { + return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32) +} + +filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li} + +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_dir = fi.mode & File_Mode_Dir != 0 - - windows_set_file_info_times(&fi, d) - - fi.fullpath, e = full_path_from_name(name) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } -@(private, require_results) -file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_dir = fi.mode & File_Mode_Dir != 0 - - windows_set_file_info_times(&fi, d) - - fi.fullpath, e = full_path_from_name(name) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } -@(private, require_results) -file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - err := get_last_error() - return {}, err + return {}, _get_platform_error() } ti: win32.FILE_ATTRIBUTE_TAG_INFO if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { - err := get_last_error() - if err != ERROR_INVALID_PARAMETER { + err := _get_platform_error() + if perr, ok := is_platform_error(err); ok && perr != i32(win32.ERROR_INVALID_PARAMETER) { return {}, err } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 } - fi: File_Info - fi.fullpath = path fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) - fi.is_dir = fi.mode & File_Mode_Dir != 0 - - windows_set_file_info_times(&fi, &d) - + fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) + fi.type = type + fi.mode |= mode + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) return fi, nil } + +reserved_names := [?]string{ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +} + +_is_reserved_name :: proc(path: string) -> bool { + if len(path) == 0 { + return false + } + for reserved in reserved_names { + if strings.equal_fold(path, reserved) { + return true + } + } + return false +} + +_volume_name_len :: proc(path: string) -> (length: int) { + if len(path) < 2 { + return 0 + } + + if path[1] == ':' { + switch path[0] { + case 'a'..='z', 'A'..='Z': + return 2 + } + } + + /* + See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + Further allowed paths can be of the form of: + - \\server\share or \\server\share\more\path + - \\?\C:\... + - \\.\PhysicalDriveX + */ + // Any remaining kind of path has to start with two slashes. + if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) { + return 0 + } + + // Device path. The volume name is the whole string + if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) { + return len(path) + } + + // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share` + prefix := 2 + + // File namespace. + if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) { + if _is_path_separator(path[4]) { + // `\\?\\` UNC path in file namespace + prefix = 5 + } + + if len(path) >= 6 && path[5] == ':' { + switch path[4] { + case 'a'..='z', 'A'..='Z': + return 6 + case: + return 0 + } + } + } + + // UNC path, minimum version of the volume is `\\h\s` for host, share. + // Can also contain an IP address in the host position. + slash_count := 0 + for i in prefix.. 0 { + slash_count += 1 + + if slash_count == 2 { + return i + } + } + } + + return len(path) +} \ No newline at end of file diff --git a/core/os/os2/temp_file.odin b/core/os/temp_file.odin similarity index 100% rename from core/os/os2/temp_file.odin rename to core/os/temp_file.odin diff --git a/core/os/os2/temp_file_js.odin b/core/os/temp_file_js.odin similarity index 100% rename from core/os/os2/temp_file_js.odin rename to core/os/temp_file_js.odin diff --git a/core/os/os2/temp_file_linux.odin b/core/os/temp_file_linux.odin similarity index 100% rename from core/os/os2/temp_file_linux.odin rename to core/os/temp_file_linux.odin diff --git a/core/os/os2/temp_file_posix.odin b/core/os/temp_file_posix.odin similarity index 100% rename from core/os/os2/temp_file_posix.odin rename to core/os/temp_file_posix.odin diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/temp_file_wasi.odin similarity index 100% rename from core/os/os2/temp_file_wasi.odin rename to core/os/temp_file_wasi.odin diff --git a/core/os/os2/temp_file_windows.odin b/core/os/temp_file_windows.odin similarity index 100% rename from core/os/os2/temp_file_windows.odin rename to core/os/temp_file_windows.odin diff --git a/core/os/os2/user.odin b/core/os/user.odin similarity index 100% rename from core/os/os2/user.odin rename to core/os/user.odin diff --git a/core/os/os2/user_posix.odin b/core/os/user_posix.odin similarity index 100% rename from core/os/os2/user_posix.odin rename to core/os/user_posix.odin diff --git a/core/os/os2/user_windows.odin b/core/os/user_windows.odin similarity index 100% rename from core/os/os2/user_windows.odin rename to core/os/user_windows.odin diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 00f5bcc3f..178d5f986 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -2,7 +2,7 @@ #+build !js package filepath -import os "core:os/os2" +import "core:os" // match states whether "name" matches the shell pattern // Pattern syntax is: diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 2f2f7996c..58dc06103 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -2,8 +2,8 @@ // To process paths such as URLs that depend on forward slashes regardless of the OS, use the slashpath package. package filepath -import os "core:os/os2" -import "core:strings" +import "core:os" +import "core:strings" SEPARATOR_CHARS :: `/\` diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 00b063bc7..2e2a4ff54 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -2,7 +2,7 @@ #+build !js package filepath -import os "core:os/os2" +import "core:os" Walker :: os.Walker @@ -43,9 +43,9 @@ If an error occurred opening a directory, you may get zero'd info struct and Example: package main - import "core:fmt" - import "core:strings" - import os "core:os/os2" + import "core:fmt" + import "core:strings" + import "core:os" main :: proc() { w := os.walker_create("core") diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index b079c2eb2..281eaa82d 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -2,8 +2,8 @@ package spall import "base:intrinsics" -import os "core:os/os2" -import "core:time" +import "core:os" +import "core:time" // File Format diff --git a/core/terminal/internal_os.odin b/core/terminal/internal_os.odin index 841803766..127cbae54 100644 --- a/core/terminal/internal_os.odin +++ b/core/terminal/internal_os.odin @@ -3,9 +3,9 @@ #+private package terminal -import "base:runtime" -import os "core:os/os2" -import "core:strings" +import "base:runtime" +import "core:os" +import "core:strings" // Reference documentation: // diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin index 751ef85cf..83e64c6d8 100644 --- a/core/terminal/terminal_posix.odin +++ b/core/terminal/terminal_posix.odin @@ -2,8 +2,8 @@ #+build linux, darwin, netbsd, openbsd, freebsd, haiku package terminal -import "base:runtime" -import os "core:os/os2" +import "base:runtime" +import "core:os" _is_terminal :: proc "contextless" (f: ^os.File) -> bool { return os.is_tty(f) diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index 78d21952b..6c77330b5 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -1,9 +1,9 @@ #+private package terminal -import "base:runtime" -import os "core:os/os2" -import "core:sys/windows" +import "base:runtime" +import "core:os" +import "core:sys/windows" _is_terminal :: proc "contextless" (f: ^os.File) -> bool { return os.is_tty(f) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 8873bc973..8f26aa6c6 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -10,24 +10,24 @@ package testing Feoramund: Total rewrite. */ -import "base:intrinsics" -import "base:runtime" -import "core:bytes" -@require import "core:encoding/base64" -@require import "core:encoding/json" -import "core:fmt" -import "core:io" -@require import "core:log" -import "core:math/rand" -import "core:mem" -import os "core:os/os2" -import "core:slice" -@require import "core:strings" -import "core:sync/chan" -import "core:terminal" -import "core:terminal/ansi" -import "core:thread" -import "core:time" +import "base:intrinsics" +import "base:runtime" +import "core:bytes" +@(require) import "core:encoding/base64" +@(require) import "core:encoding/json" +import "core:fmt" +import "core:io" +@(require) import "core:log" +import "core:math/rand" +import "core:mem" +import "core:os" +import "core:slice" +@(require) import "core:strings" +import "core:sync/chan" +import "core:terminal" +import "core:terminal/ansi" +import "core:thread" +import "core:time" // Specify how many threads to use when running tests. TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index badee802d..fb19a0115 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -11,11 +11,11 @@ package testing blob1807: Windows Win32 API rewrite. */ -import "base:intrinsics" -import "core:c/libc" -import os "core:os/os2" -import "core:sync" -import "core:terminal/ansi" +import "base:intrinsics" +import "core:c/libc" +import "core:os" +import "core:sync" +import "core:terminal/ansi" @(private="file") stop_runner_flag: libc.sig_atomic_t diff --git a/core/text/i18n/i18_js.odin b/core/text/i18n/i18_js.odin index 743718942..73e8535a5 100644 --- a/core/text/i18n/i18_js.odin +++ b/core/text/i18n/i18_js.odin @@ -10,8 +10,6 @@ package i18n List of contributors: Jeroen van Rijn: Initial implementation. */ -import os "core:os/os2" - @(private) parse_qt :: proc { parse_qt_linguist_from_bytes } diff --git a/core/text/i18n/i18n_os.odin b/core/text/i18n/i18n_os.odin index db82a9cf6..7a7995612 100644 --- a/core/text/i18n/i18n_os.odin +++ b/core/text/i18n/i18n_os.odin @@ -11,7 +11,7 @@ package i18n Jeroen van Rijn: Initial implementation. */ import "base:runtime" -import os "core:os/os2" +import "core:os" @(private) read_file :: proc(filename: string, allocator: runtime.Allocator) -> (data: []u8, err: Error) { diff --git a/core/text/regex/common/os.odin b/core/text/regex/common/os.odin index 1d38d687c..bde57f77f 100644 --- a/core/text/regex/common/os.odin +++ b/core/text/regex/common/os.odin @@ -10,7 +10,7 @@ package regex_common Feoramund: Initial implementation. */ -@require import os "core:os/os2" +@require import "core:os" when ODIN_DEBUG_REGEX { debug_stream := os.stderr.stream diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin index 29b60dbe2..d91763661 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -189,7 +189,7 @@ Example: import "core:fmt" import "core:io" - import os "core:os/os2" + import "core:os" import "core:text/table" scripts :: proc(w: io.Writer) { @@ -265,10 +265,10 @@ corners and dividers. Example: package main - import "core:fmt" - import "core:io" - import os "core:os/os2" - import "core:text/table" + import "core:fmt" + import "core:io" + import "core:os" + import "core:text/table" box_drawing :: proc(w: io.Writer) { t: table.Table diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin index db5ae4602..675fa6b10 100644 --- a/core/text/table/utility.odin +++ b/core/text/table/utility.odin @@ -2,9 +2,9 @@ #+build !js package text_table -import "core:io" -import os "core:os/os2" -import "core:strings" +import "core:io" +import "core:os" +import "core:strings" stdio_writer :: proc() -> io.Writer { return os.to_stream(os.stdout) diff --git a/core/time/timezone/tz_os.odin b/core/time/timezone/tz_os.odin index 2ab7cfa6c..fae4980c3 100644 --- a/core/time/timezone/tz_os.odin +++ b/core/time/timezone/tz_os.odin @@ -2,7 +2,7 @@ #+build !js package timezone -import os "core:os/os2" +import "core:os" import "core:time/datetime" load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin index 60a20e57c..3939b3265 100644 --- a/core/time/timezone/tz_unix.odin +++ b/core/time/timezone/tz_unix.odin @@ -2,10 +2,10 @@ #+private package timezone -import os "core:os/os2" -import "core:strings" -import "core:time/datetime" -import "core:path/filepath" +import "core:os" +import "core:strings" +import "core:time/datetime" +import "core:path/filepath" local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { local_str, ok := os.lookup_env("TZ", allocator) diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 02958ad26..54f73370c 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -1,7 +1,7 @@ package xml_example import "core:encoding/xml" -import os "core:os/os2" +import "core:os" import path "core:path/filepath" import "core:strings" import "core:strconv" diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index c5f627653..3576fc027 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -117,7 +117,7 @@ package all @(require) import "core:prof/spall" @(require) import "core:os" -@(require) import "core:os/os2" +@(require) import "core:os/old" @(require) import "core:path/slashpath" @(require) import "core:path/filepath" diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index 17b3ca619..a4fee030c 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -6,7 +6,7 @@ import "core:testing" TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa" -import os "core:os/os2" +import "core:os" @test test_read :: proc(t: ^testing.T) { diff --git a/tests/core/flags/test_core_flags.odin b/tests/core/flags/test_core_flags.odin index 1aee7f69c..834f6b630 100644 --- a/tests/core/flags/test_core_flags.odin +++ b/tests/core/flags/test_core_flags.odin @@ -1,16 +1,16 @@ package test_core_flags -import "base:runtime" -import "core:bytes" -import "core:flags" -import "core:fmt" -@require import "core:log" -import "core:math" -@require import "core:net" -import os "core:os/os2" -import "core:strings" -import "core:testing" -import "core:time/datetime" +import "base:runtime" +import "core:bytes" +import "core:flags" +import "core:fmt" +@(require) import "core:log" +import "core:math" +@(require) import "core:net" +import "core:os" +import "core:strings" +import "core:testing" +import "core:time/datetime" Custom_Data :: struct { a: int, diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index eb4d79317..301e7bb94 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -1,12 +1,12 @@ package test_core_io -import "core:bufio" -import "core:bytes" -import "core:io" -import "core:log" -import os "core:os/os2" -import "core:strings" -import "core:testing" +import "core:bufio" +import "core:bytes" +import "core:io" +import "core:log" +import "core:os" +import "core:strings" +import "core:testing" Passed_Tests :: distinct io.Stream_Mode_Set diff --git a/tests/core/nbio/fs.odin b/tests/core/nbio/fs.odin index 6e079f96e..1b10c03c9 100644 --- a/tests/core/nbio/fs.odin +++ b/tests/core/nbio/fs.odin @@ -1,9 +1,9 @@ package tests_nbio -import "core:nbio" -import "core:testing" -import "core:time" -import os "core:os/os2" +import "core:nbio" +import "core:testing" +import "core:time" +import "core:os" @(test) close_invalid_handle :: proc(t: ^testing.T) { diff --git a/tests/core/nbio/nbio.odin b/tests/core/nbio/nbio.odin index 2f454f55b..6c3fd0e8c 100644 --- a/tests/core/nbio/nbio.odin +++ b/tests/core/nbio/nbio.odin @@ -1,11 +1,11 @@ package tests_nbio -import "core:log" -import "core:nbio" -import "core:testing" -import "core:thread" -import "core:time" -import os "core:os/os2" +import "core:log" +import "core:nbio" +import "core:testing" +import "core:thread" +import "core:time" +import "core:os" ev :: testing.expect_value e :: testing.expect diff --git a/tests/core/normal.odin b/tests/core/normal.odin index 6b31b9d56..4708ed700 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -37,7 +37,7 @@ download_assets :: proc "contextless" () { @(require) import "net" @(require) import "odin" @(require) import "os" -@(require) import "os/os2" +@(require) import "os/old" @(require) import "reflect" @(require) import "runtime" @(require) import "slice" diff --git a/tests/core/os/os2/dir.odin b/tests/core/os/dir.odin similarity index 82% rename from tests/core/os/os2/dir.odin rename to tests/core/os/dir.odin index 8ef333219..464abed98 100644 --- a/tests/core/os/os2/dir.odin +++ b/tests/core/os/dir.odin @@ -1,14 +1,14 @@ -package tests_core_os_os2 +package tests_core_os -import os "core:os/os2" -import "core:log" -import "core:slice" -import "core:testing" -import "core:strings" +import "core:os" +import "core:log" +import "core:slice" +import "core:testing" +import "core:strings" @(test) test_read_dir :: proc(t: ^testing.T) { - path, err_join := os.join_path({#directory, "../dir"}, context.allocator) + path, err_join := os.join_path({#directory, "dir"}, context.allocator) defer delete(path) fis, err_read := os.read_all_directory_by_path(path, context.allocator) @@ -17,7 +17,7 @@ test_read_dir :: proc(t: ^testing.T) { slice.sort_by_key(fis, proc(fi: os.File_Info) -> string { return fi.name }) if err_read == .Unsupported { - log.warn("os2 directory functionality is unsupported, skipping test") + log.warn("core:os directory functionality is unsupported, skipping test") return } @@ -34,7 +34,7 @@ test_read_dir :: proc(t: ^testing.T) { @(test) test_walker :: proc(t: ^testing.T) { - path, err := os.join_path({#directory, "../dir"}, context.allocator) + path, err := os.join_path({#directory, "dir"}, context.allocator) defer delete(path) testing.expect_value(t, err, nil) @@ -46,7 +46,7 @@ test_walker :: proc(t: ^testing.T) { @(test) test_walker_file :: proc(t: ^testing.T) { - path, err_join := os.join_path({#directory, "../dir"}, context.allocator) + path, err_join := os.join_path({#directory, "dir"}, context.allocator) defer delete(path) testing.expect_value(t, err_join, nil) @@ -95,7 +95,7 @@ test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) { } if _, err := os.walker_error(w); err == .Unsupported { - log.warn("os2 directory functionality is unsupported, skipping test") + log.warn("core:os directory functionality is unsupported, skipping test") return } diff --git a/tests/core/os/os2/file.odin b/tests/core/os/file.odin similarity index 90% rename from tests/core/os/os2/file.odin rename to tests/core/os/file.odin index 0152a2008..aed57c26c 100644 --- a/tests/core/os/os2/file.odin +++ b/tests/core/os/file.odin @@ -1,7 +1,7 @@ -package tests_core_os_os2 +package tests_core_os -import os "core:os/os2" -import "core:testing" +import "core:os" +import "core:testing" @(test) test_clone :: proc(t: ^testing.T) { diff --git a/tests/core/os/os.odin b/tests/core/os/old/os.odin similarity index 97% rename from tests/core/os/os.odin rename to tests/core/os/old/os.odin index 1510bad31..9925cf708 100644 --- a/tests/core/os/os.odin +++ b/tests/core/os/old/os.odin @@ -1,8 +1,8 @@ -package test_core_os +package test_core_os_old import "core:c/libc" import win32 "core:sys/windows" -import "core:os" +import os "core:os/old" import "core:slice" import "core:testing" import "core:log" diff --git a/tests/core/os/os2/path.odin b/tests/core/os/path.odin similarity index 97% rename from tests/core/os/os2/path.odin rename to tests/core/os/path.odin index 868023c86..cdfaed56f 100644 --- a/tests/core/os/os2/path.odin +++ b/tests/core/os/path.odin @@ -1,11 +1,11 @@ -package tests_core_os_os2 +package tests_core_os -import "core:fmt" -import os "core:os/os2" -import "core:log" -import "core:testing" -import "core:slice" -import "core:strings" +import "core:fmt" +import "core:os" +import "core:log" +import "core:testing" +import "core:slice" +import "core:strings" @(test) test_executable :: proc(t: ^testing.T) { @@ -351,12 +351,12 @@ glob_tests := []Glob_Test{ err = {}, }, { - pattern = ODIN_ROOT + "tests/core/os/os2/*.odin", + pattern = ODIN_ROOT + "tests/core/os/*.odin", matches = { - ODIN_ROOT + "tests/core/os/os2/dir.odin", - ODIN_ROOT + "tests/core/os/os2/file.odin", - ODIN_ROOT + "tests/core/os/os2/path.odin", - ODIN_ROOT + "tests/core/os/os2/process.odin", + ODIN_ROOT + "tests/core/os/dir.odin", + ODIN_ROOT + "tests/core/os/file.odin", + ODIN_ROOT + "tests/core/os/path.odin", + ODIN_ROOT + "tests/core/os/process.odin", }, err = {}, }, diff --git a/tests/core/os/os2/process.odin b/tests/core/os/process.odin similarity index 84% rename from tests/core/os/os2/process.odin rename to tests/core/os/process.odin index c530b4c79..adb65e95f 100644 --- a/tests/core/os/os2/process.odin +++ b/tests/core/os/process.odin @@ -1,9 +1,9 @@ #+build !windows -package tests_core_os_os2 +package tests_core_os -import os "core:os/os2" -import "core:log" -import "core:testing" +import "core:os" +import "core:log" +import "core:testing" @(test) test_process_exec :: proc(t: ^testing.T) { diff --git a/tests/core/sys/kqueue/structs.odin b/tests/core/sys/kqueue/structs.odin index edf1fdd1e..15ec3f841 100644 --- a/tests/core/sys/kqueue/structs.odin +++ b/tests/core/sys/kqueue/structs.odin @@ -1,9 +1,9 @@ #+build darwin, freebsd, openbsd, netbsd package tests_core_sys_kqueue -import "core:strings" -import "core:testing" -import os "core:os/os2" +import "core:strings" +import "core:testing" +import "core:os" @(test) structs :: proc(t: ^testing.T) { diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 6694de709..be59d9b4d 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -1,6 +1,6 @@ package documentation_tester -import os "core:os/os2" +import "core:os" import "core:fmt" import "core:strings" import "core:odin/ast" @@ -267,7 +267,7 @@ write_test_suite :: proc(example_tests: []Example_Test) { `#+private package documentation_verification -import os "core:os/os2" +import "core:os" import "core:mem" import "core:io" import "core:fmt" diff --git a/vendor/OpenGL/helpers.odin b/vendor/OpenGL/helpers.odin index bce60ee80..9c9c74d09 100644 --- a/vendor/OpenGL/helpers.odin +++ b/vendor/OpenGL/helpers.odin @@ -3,7 +3,7 @@ package vendor_gl // Helper for loading shaders into a program -import os "core:os/os2" +import "core:os" import "core:fmt" import "core:strings" @(require) import "core:time" diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin index 8c259412d..d04df044c 100644 --- a/vendor/fontstash/fontstash_os.odin +++ b/vendor/fontstash/fontstash_os.odin @@ -1,8 +1,8 @@ #+build !js package fontstash -import "core:log" -import os "core:os/os2" +import "core:log" +import "core:os" // 'fontIndex' controls which font you want to load within a multi-font format such // as TTC. Leave it as zero if you are loading a single-font format such as TTF. diff --git a/vendor/libc-shim/stdio_os.odin b/vendor/libc-shim/stdio_os.odin index db40fb250..f6d30a227 100644 --- a/vendor/libc-shim/stdio_os.odin +++ b/vendor/libc-shim/stdio_os.odin @@ -2,9 +2,9 @@ #+build !js package odin_libc -import "core:io" -import "core:c" -import os "core:os/os2" +import "core:io" +import "core:c" +import "core:os" _fopen :: proc(path, _mode: cstring) -> FILE { flags: os.File_Flags From 8366e7094a976b9d7646e3f6eada7e5e1820f117 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 16:27:53 +0100 Subject: [PATCH 54/59] Fix up ABI name for core:os and core:os/old --- core/flags/internal_rtti.odin | 2 +- core/os/allocators.odin | 2 +- core/os/dir.odin | 2 +- core/os/dir_js.odin | 2 +- core/os/dir_linux.odin | 2 +- core/os/dir_posix.odin | 2 +- core/os/dir_posix_darwin.odin | 2 +- core/os/dir_walker.odin | 2 +- core/os/dir_wasi.odin | 2 +- core/os/dir_windows.odin | 2 +- core/os/doc.odin | 6 +++--- core/os/env.odin | 2 +- core/os/env_js.odin | 2 +- core/os/env_linux.odin | 2 +- core/os/env_posix.odin | 2 +- core/os/env_wasi.odin | 2 +- core/os/env_windows.odin | 2 +- core/os/errors.odin | 2 +- core/os/errors_js.odin | 2 +- core/os/errors_linux.odin | 2 +- core/os/errors_posix.odin | 2 +- core/os/errors_wasi.odin | 2 +- core/os/errors_windows.odin | 2 +- core/os/file.odin | 2 +- core/os/file_js.odin | 2 +- core/os/file_linux.odin | 2 +- core/os/file_posix.odin | 2 +- core/os/file_posix_darwin.odin | 2 +- core/os/file_posix_freebsd.odin | 2 +- core/os/file_posix_netbsd.odin | 2 +- core/os/file_posix_other.odin | 2 +- core/os/file_stream.odin | 2 +- core/os/file_util.odin | 2 +- core/os/file_wasi.odin | 2 +- core/os/file_windows.odin | 2 +- core/os/heap.odin | 2 +- core/os/heap_js.odin | 2 +- core/os/heap_linux.odin | 2 +- core/os/heap_posix.odin | 2 +- core/os/heap_wasi.odin | 2 +- core/os/heap_windows.odin | 2 +- core/os/internal_util.odin | 2 +- core/os/old/dir_unix.odin | 2 +- core/os/old/dir_windows.odin | 2 +- core/os/old/env_windows.odin | 2 +- core/os/old/errors.odin | 2 +- core/os/old/os.odin | 2 +- core/os/old/os_darwin.odin | 2 +- core/os/old/os_essence.odin | 2 +- core/os/old/os_freebsd.odin | 2 +- core/os/old/os_freestanding.odin | 4 ++-- core/os/old/os_haiku.odin | 2 +- core/os/old/os_js.odin | 2 +- core/os/old/os_linux.odin | 17 +++-------------- core/os/old/os_netbsd.odin | 2 +- core/os/old/os_openbsd.odin | 2 +- core/os/old/os_wasi.odin | 2 +- core/os/old/os_windows.odin | 2 +- core/os/old/stat.odin | 2 +- core/os/old/stat_unix.odin | 2 +- core/os/old/stat_windows.odin | 2 +- core/os/old/stream.odin | 2 +- core/os/path.odin | 2 +- core/os/path_darwin.odin | 2 +- core/os/path_freebsd.odin | 2 +- core/os/path_js.odin | 2 +- core/os/path_linux.odin | 2 +- core/os/path_netbsd.odin | 2 +- core/os/path_openbsd.odin | 2 +- core/os/path_posix.odin | 2 +- core/os/path_posixfs.odin | 2 +- core/os/path_wasi.odin | 2 +- core/os/path_windows.odin | 2 +- core/os/pipe.odin | 4 ++-- core/os/pipe_js.odin | 2 +- core/os/pipe_linux.odin | 2 +- core/os/pipe_posix.odin | 2 +- core/os/pipe_wasi.odin | 2 +- core/os/pipe_windows.odin | 2 +- core/os/process.odin | 2 +- core/os/process_freebsd.odin | 2 +- core/os/process_js.odin | 2 +- core/os/process_linux.odin | 2 +- core/os/process_netbsd.odin | 2 +- core/os/process_openbsd.odin | 2 +- core/os/process_posix.odin | 2 +- core/os/process_posix_darwin.odin | 2 +- core/os/process_posix_other.odin | 2 +- core/os/process_wasi.odin | 2 +- core/os/process_windows.odin | 2 +- core/os/stat.odin | 2 +- core/os/stat_js.odin | 2 +- core/os/stat_linux.odin | 2 +- core/os/stat_posix.odin | 2 +- core/os/stat_wasi.odin | 2 +- core/os/stat_windows.odin | 2 +- core/os/temp_file.odin | 2 +- core/os/temp_file_js.odin | 2 +- core/os/temp_file_linux.odin | 2 +- core/os/temp_file_posix.odin | 2 +- core/os/temp_file_wasi.odin | 2 +- core/os/temp_file_windows.odin | 2 +- core/os/user.odin | 2 +- core/os/user_posix.odin | 2 +- core/os/user_windows.odin | 2 +- tests/core/io/test_core_io.odin | 12 ++++++------ 106 files changed, 117 insertions(+), 128 deletions(-) diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index d5e8726e2..1d86f4bce 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -258,7 +258,7 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: // NOTE(Feoramund): os.Error is system-dependent, and there's // currently no good way to translate them all into strings. // - // The upcoming `os2` package will hopefully solve this. + // The upcoming `core:os` package will hopefully solve this. // // We can at least provide the number for now, so the user can look // it up. diff --git a/core/os/allocators.odin b/core/os/allocators.odin index 36a7d72be..e49d416e1 100644 --- a/core/os/allocators.odin +++ b/core/os/allocators.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/dir.odin b/core/os/dir.odin index 9ad5f451e..a2fba81e4 100644 --- a/core/os/dir.odin +++ b/core/os/dir.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" import "core:slice" diff --git a/core/os/dir_js.odin b/core/os/dir_js.odin index d8f7c6202..8c45cf63b 100644 --- a/core/os/dir_js.odin +++ b/core/os/dir_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:intrinsics" diff --git a/core/os/dir_linux.odin b/core/os/dir_linux.odin index 34346c02f..1ca2ef9b4 100644 --- a/core/os/dir_linux.odin +++ b/core/os/dir_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:sys/linux" diff --git a/core/os/dir_posix.odin b/core/os/dir_posix.odin index d9fa16f8d..c67a37430 100644 --- a/core/os/dir_posix.odin +++ b/core/os/dir_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "core:sys/posix" diff --git a/core/os/dir_posix_darwin.odin b/core/os/dir_posix_darwin.odin index 3cae50d25..e67c917b4 100644 --- a/core/os/dir_posix_darwin.odin +++ b/core/os/dir_posix_darwin.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:sys/darwin" diff --git a/core/os/dir_walker.odin b/core/os/dir_walker.odin index 4dce884a8..b510b7a2a 100644 --- a/core/os/dir_walker.odin +++ b/core/os/dir_walker.odin @@ -1,4 +1,4 @@ -package os2 +package os import "core:container/queue" diff --git a/core/os/dir_wasi.odin b/core/os/dir_wasi.odin index 9804f07fd..05d74be31 100644 --- a/core/os/dir_wasi.odin +++ b/core/os/dir_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:slice" import "base:intrinsics" diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index a4dadca75..1168fe18b 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "core:time" diff --git a/core/os/doc.odin b/core/os/doc.odin index 2ebdd0912..effb5582c 100644 --- a/core/os/doc.odin +++ b/core/os/doc.odin @@ -1,6 +1,6 @@ -// Package os provides a platform-independent interface to operating system functionality. +// package os_old provides a platform-independent interface to operating system functionality. // The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number. // -// The package os interface is intended to be uniform across all operating systems. +// The package os_old interface is intended to be uniform across all operating systems. // Features not generally available appear in the system-specific packages under core:sys/*. -package os2 +package os diff --git a/core/os/env.odin b/core/os/env.odin index 310d45af1..0b8138e6b 100644 --- a/core/os/env.odin +++ b/core/os/env.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" import "core:strings" diff --git a/core/os/env_js.odin b/core/os/env_js.odin index c1d94ba4a..644af61bd 100644 --- a/core/os/env_js.odin +++ b/core/os/env_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/env_linux.odin b/core/os/env_linux.odin index 7855fbfed..e55c59293 100644 --- a/core/os/env_linux.odin +++ b/core/os/env_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "base:intrinsics" diff --git a/core/os/env_posix.odin b/core/os/env_posix.odin index 72a1daf18..5d46cf7fc 100644 --- a/core/os/env_posix.odin +++ b/core/os/env_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/env_wasi.odin b/core/os/env_wasi.odin index cb40667cf..03a56421d 100644 --- a/core/os/env_wasi.odin +++ b/core/os/env_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index d389f8860..dfbc5c454 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import win32 "core:sys/windows" import "base:runtime" diff --git a/core/os/errors.odin b/core/os/errors.odin index 508d824b3..a60233f09 100644 --- a/core/os/errors.odin +++ b/core/os/errors.odin @@ -1,4 +1,4 @@ -package os2 +package os import "core:io" import "base:runtime" diff --git a/core/os/errors_js.odin b/core/os/errors_js.odin index c92d36736..34779807d 100644 --- a/core/os/errors_js.odin +++ b/core/os/errors_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os _Platform_Error :: enum i32 {} diff --git a/core/os/errors_linux.odin b/core/os/errors_linux.odin index a7556c306..891b4177c 100644 --- a/core/os/errors_linux.odin +++ b/core/os/errors_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:sys/linux" diff --git a/core/os/errors_posix.odin b/core/os/errors_posix.odin index 8a9ca07df..5233fec1a 100644 --- a/core/os/errors_posix.odin +++ b/core/os/errors_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "core:sys/posix" diff --git a/core/os/errors_wasi.odin b/core/os/errors_wasi.odin index b88e5b81e..a0377ce96 100644 --- a/core/os/errors_wasi.odin +++ b/core/os/errors_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/errors_windows.odin b/core/os/errors_windows.odin index 404560f98..639780337 100644 --- a/core/os/errors_windows.odin +++ b/core/os/errors_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "core:slice" diff --git a/core/os/file.odin b/core/os/file.odin index bf7ebaeb5..61582c528 100644 --- a/core/os/file.odin +++ b/core/os/file.odin @@ -1,4 +1,4 @@ -package os2 +package os import "core:io" import "core:time" diff --git a/core/os/file_js.odin b/core/os/file_js.odin index 91ee7f02e..5d921c9b0 100644 --- a/core/os/file_js.odin +++ b/core/os/file_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/file_linux.odin b/core/os/file_linux.odin index f5f2ebdd7..d4223ba5d 100644 --- a/core/os/file_linux.odin +++ b/core/os/file_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "core:io" diff --git a/core/os/file_posix.odin b/core/os/file_posix.odin index ef53bf116..006f25e11 100644 --- a/core/os/file_posix.odin +++ b/core/os/file_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/file_posix_darwin.odin b/core/os/file_posix_darwin.odin index 521fb345b..5522124ae 100644 --- a/core/os/file_posix_darwin.odin +++ b/core/os/file_posix_darwin.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/file_posix_freebsd.odin b/core/os/file_posix_freebsd.odin index 05d031930..4ed9ecc1e 100644 --- a/core/os/file_posix_freebsd.odin +++ b/core/os/file_posix_freebsd.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/file_posix_netbsd.odin b/core/os/file_posix_netbsd.odin index f96c227ba..791836c00 100644 --- a/core/os/file_posix_netbsd.odin +++ b/core/os/file_posix_netbsd.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/file_posix_other.odin b/core/os/file_posix_other.odin index 8871a0062..6430c9fb6 100644 --- a/core/os/file_posix_other.odin +++ b/core/os/file_posix_other.odin @@ -1,6 +1,6 @@ #+private #+build openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/file_stream.odin b/core/os/file_stream.odin index af6e50921..cee1bef47 100644 --- a/core/os/file_stream.odin +++ b/core/os/file_stream.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:intrinsics" import "base:runtime" diff --git a/core/os/file_util.odin b/core/os/file_util.odin index f81dc2190..505432338 100644 --- a/core/os/file_util.odin +++ b/core/os/file_util.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" import "core:strconv" diff --git a/core/os/file_wasi.odin b/core/os/file_wasi.odin index 78aa90699..116c237d6 100644 --- a/core/os/file_wasi.odin +++ b/core/os/file_wasi.odin @@ -1,6 +1,6 @@ #+feature global-context #+private -package os2 +package os import "base:runtime" diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index 0e3448dd7..3a9e90fed 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/heap.odin b/core/os/heap.odin index b1db54dc7..356e60b4d 100644 --- a/core/os/heap.odin +++ b/core/os/heap.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/heap_js.odin b/core/os/heap_js.odin index 15990b517..5f8d9bd35 100644 --- a/core/os/heap_js.odin +++ b/core/os/heap_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/heap_linux.odin b/core/os/heap_linux.odin index 1d1f12726..69c2b6edf 100644 --- a/core/os/heap_linux.odin +++ b/core/os/heap_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/heap_posix.odin b/core/os/heap_posix.odin index 1b52aed75..5bff96de6 100644 --- a/core/os/heap_posix.odin +++ b/core/os/heap_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/heap_wasi.odin b/core/os/heap_wasi.odin index 7da3c4845..066a43f85 100644 --- a/core/os/heap_wasi.odin +++ b/core/os/heap_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/heap_windows.odin b/core/os/heap_windows.odin index 7fd4529a0..e0e62a91f 100644 --- a/core/os/heap_windows.odin +++ b/core/os/heap_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:mem" import win32 "core:sys/windows" diff --git a/core/os/internal_util.odin b/core/os/internal_util.odin index 9616af8b0..a279e9bee 100644 --- a/core/os/internal_util.odin +++ b/core/os/internal_util.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:intrinsics" import "base:runtime" diff --git a/core/os/old/dir_unix.odin b/core/os/old/dir_unix.odin index c3dd844ef..6d2a44626 100644 --- a/core/os/old/dir_unix.odin +++ b/core/os/old/dir_unix.odin @@ -1,5 +1,5 @@ #+build darwin, linux, netbsd, freebsd, openbsd, haiku -package os +package os_old_old import "core:strings" diff --git a/core/os/old/dir_windows.odin b/core/os/old/dir_windows.odin index 40f4b9e9b..b81787872 100644 --- a/core/os/old/dir_windows.odin +++ b/core/os/old/dir_windows.odin @@ -1,4 +1,4 @@ -package os +package os_old import win32 "core:sys/windows" import "core:strings" diff --git a/core/os/old/env_windows.odin b/core/os/old/env_windows.odin index ef658b0a1..f9480340c 100644 --- a/core/os/old/env_windows.odin +++ b/core/os/old/env_windows.odin @@ -1,4 +1,4 @@ -package os +package os_old import win32 "core:sys/windows" import "base:runtime" diff --git a/core/os/old/errors.odin b/core/os/old/errors.odin index fcf70ec74..a6632fe1e 100644 --- a/core/os/old/errors.odin +++ b/core/os/old/errors.odin @@ -1,4 +1,4 @@ -package os +package os_old import "base:intrinsics" import "base:runtime" diff --git a/core/os/old/os.odin b/core/os/old/os.odin index da7b0c151..01e93126d 100644 --- a/core/os/old/os.odin +++ b/core/os/old/os.odin @@ -1,5 +1,5 @@ // Cross-platform `OS` interactions like file `I/O`. -package os +package os_old import "base:intrinsics" import "base:runtime" diff --git a/core/os/old/os_darwin.odin b/core/os/old/os_darwin.odin index 92a636255..6a6efe4e2 100644 --- a/core/os/old/os_darwin.odin +++ b/core/os/old/os_darwin.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import dl "system:dl" foreign import libc "system:System" diff --git a/core/os/old/os_essence.odin b/core/os/old/os_essence.odin index 75c4c1156..8156bb496 100644 --- a/core/os/old/os_essence.odin +++ b/core/os/old/os_essence.odin @@ -1,4 +1,4 @@ -package os +package os_old import "core:sys/es" diff --git a/core/os/old/os_freebsd.odin b/core/os/old/os_freebsd.odin index 82b5a2f0f..a1ecf2aff 100644 --- a/core/os/old/os_freebsd.odin +++ b/core/os/old/os_freebsd.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import dl "system:dl" foreign import libc "system:c" diff --git a/core/os/old/os_freestanding.odin b/core/os/old/os_freestanding.odin index c22a6d7d5..def000aae 100644 --- a/core/os/old/os_freestanding.odin +++ b/core/os/old/os_freestanding.odin @@ -1,4 +1,4 @@ #+build freestanding -package os +package os_old -#panic("package os does not support a freestanding target") +#panic("package os_old does not support a freestanding target") diff --git a/core/os/old/os_haiku.odin b/core/os/old/os_haiku.odin index ad984e33c..a85f2d5c1 100644 --- a/core/os/old/os_haiku.odin +++ b/core/os/old/os_haiku.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import lib "system:c" diff --git a/core/os/old/os_js.odin b/core/os/old/os_js.odin index 1870218d3..cefabbf4d 100644 --- a/core/os/old/os_js.odin +++ b/core/os/old/os_js.odin @@ -1,5 +1,5 @@ #+build js -package os +package os_old foreign import "odin_env" diff --git a/core/os/old/os_linux.odin b/core/os/old/os_linux.odin index 4c32676c6..504f6f5b3 100644 --- a/core/os/old/os_linux.odin +++ b/core/os/old/os_linux.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import dl "system:dl" foreign import libc "system:c" @@ -7,19 +7,8 @@ import "base:runtime" import "core:strings" import "core:c" import "core:strconv" - -// NOTE(flysand): For compatibility we'll make core:os package -// depend on the old (scheduled for removal) linux package. -// Seeing that there are plans for os2, I'm imagining that *that* -// package should inherit the new sys functionality. -// The reasons for these are as follows: -// 1. It's very hard to update this package without breaking *a lot* of code. -// 2. os2 is not stable anyways, so we can break compatibility all we want -// It might be weird to bring up compatibility when Odin in it's nature isn't -// all that about compatibility. But we don't want to push experimental changes -// and have people's code break while it's still work in progress. -import unix "core:sys/unix" -import linux "core:sys/linux" +import "core:sys/unix" +import "core:sys/linux" Handle :: distinct i32 Pid :: distinct i32 diff --git a/core/os/old/os_netbsd.odin b/core/os/old/os_netbsd.odin index 640ea46cd..601e42199 100644 --- a/core/os/old/os_netbsd.odin +++ b/core/os/old/os_netbsd.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import dl "system:dl" foreign import libc "system:c" diff --git a/core/os/old/os_openbsd.odin b/core/os/old/os_openbsd.odin index bf89a21f4..95d431134 100644 --- a/core/os/old/os_openbsd.odin +++ b/core/os/old/os_openbsd.odin @@ -1,4 +1,4 @@ -package os +package os_old foreign import libc "system:c" diff --git a/core/os/old/os_wasi.odin b/core/os/old/os_wasi.odin index fe0a1fb3e..287034957 100644 --- a/core/os/old/os_wasi.odin +++ b/core/os/old/os_wasi.odin @@ -1,4 +1,4 @@ -package os +package os_old import "core:sys/wasm/wasi" import "base:runtime" diff --git a/core/os/old/os_windows.odin b/core/os/old/os_windows.odin index cb7e42f67..8081d9726 100644 --- a/core/os/old/os_windows.odin +++ b/core/os/old/os_windows.odin @@ -1,5 +1,5 @@ #+build windows -package os +package os_old import win32 "core:sys/windows" import "base:runtime" diff --git a/core/os/old/stat.odin b/core/os/old/stat.odin index 21a4961d1..fad8ff755 100644 --- a/core/os/old/stat.odin +++ b/core/os/old/stat.odin @@ -1,4 +1,4 @@ -package os +package os_old import "core:time" diff --git a/core/os/old/stat_unix.odin b/core/os/old/stat_unix.odin index 648987a07..0f7be62e2 100644 --- a/core/os/old/stat_unix.odin +++ b/core/os/old/stat_unix.odin @@ -1,5 +1,5 @@ #+build linux, darwin, freebsd, openbsd, netbsd, haiku -package os +package os_old import "core:time" diff --git a/core/os/old/stat_windows.odin b/core/os/old/stat_windows.odin index 662c9f9e6..34e5e1695 100644 --- a/core/os/old/stat_windows.odin +++ b/core/os/old/stat_windows.odin @@ -1,4 +1,4 @@ -package os +package os_old import "core:time" import "base:runtime" diff --git a/core/os/old/stream.odin b/core/os/old/stream.odin index f4e9bcdde..d94505505 100644 --- a/core/os/old/stream.odin +++ b/core/os/old/stream.odin @@ -1,4 +1,4 @@ -package os +package os_old import "core:io" diff --git a/core/os/path.odin b/core/os/path.odin index ac18b7562..e0353e43d 100644 --- a/core/os/path.odin +++ b/core/os/path.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" import "core:slice" diff --git a/core/os/path_darwin.odin b/core/os/path_darwin.odin index 65aaf1e95..dbfdb584b 100644 --- a/core/os/path_darwin.odin +++ b/core/os/path_darwin.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/path_freebsd.odin b/core/os/path_freebsd.odin index e7e4f63c9..5ec43466e 100644 --- a/core/os/path_freebsd.odin +++ b/core/os/path_freebsd.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/path_js.odin b/core/os/path_js.odin index 0c0d1424b..6ac845560 100644 --- a/core/os/path_js.odin +++ b/core/os/path_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/path_linux.odin b/core/os/path_linux.odin index 1c9927843..ca68fffb1 100644 --- a/core/os/path_linux.odin +++ b/core/os/path_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/path_netbsd.odin b/core/os/path_netbsd.odin index 815102dea..a9ceb13df 100644 --- a/core/os/path_netbsd.odin +++ b/core/os/path_netbsd.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/path_openbsd.odin b/core/os/path_openbsd.odin index cbc0346d4..55b6b7d1f 100644 --- a/core/os/path_openbsd.odin +++ b/core/os/path_openbsd.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/path_posix.odin b/core/os/path_posix.odin index 173cb6b6d..a877af3e6 100644 --- a/core/os/path_posix.odin +++ b/core/os/path_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/path_posixfs.odin b/core/os/path_posixfs.odin index 0736e73d1..aea89c60a 100644 --- a/core/os/path_posixfs.odin +++ b/core/os/path_posixfs.odin @@ -1,6 +1,6 @@ #+private #+build linux, darwin, netbsd, freebsd, openbsd, wasi -package os2 +package os // This implementation is for all systems that have POSIX-compliant filesystem paths. diff --git a/core/os/path_wasi.odin b/core/os/path_wasi.odin index f26e16158..aa7740497 100644 --- a/core/os/path_wasi.odin +++ b/core/os/path_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/path_windows.odin b/core/os/path_windows.odin index 275fe3e18..6ccab1dab 100644 --- a/core/os/path_windows.odin +++ b/core/os/path_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "core:strings" diff --git a/core/os/pipe.odin b/core/os/pipe.odin index 5d3e8368e..5254f9469 100644 --- a/core/os/pipe.odin +++ b/core/os/pipe.odin @@ -1,4 +1,4 @@ -package os2 +package os /* Create an anonymous pipe. @@ -15,7 +15,7 @@ process, that end of the pipe needs to be closed by the parent, before any data is attempted to be read. Although pipes look like files and is compatible with most file APIs in package -os2, the way it's meant to be read is different. Due to asynchronous nature of +os, the way it's meant to be read is different. Due to asynchronous nature of the communication channel, the data may not be present at the time of a read request. The other scenario is when a pipe has no data because the other end of the pipe was closed by the child process. diff --git a/core/os/pipe_js.odin b/core/os/pipe_js.odin index 253228f86..aea5e9a83 100644 --- a/core/os/pipe_js.odin +++ b/core/os/pipe_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os _pipe :: proc() -> (r, w: ^File, err: Error) { err = .Unsupported diff --git a/core/os/pipe_linux.odin b/core/os/pipe_linux.odin index bb4456e1c..561f82f80 100644 --- a/core/os/pipe_linux.odin +++ b/core/os/pipe_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:sys/linux" diff --git a/core/os/pipe_posix.odin b/core/os/pipe_posix.odin index 7c07bc068..e811e306f 100644 --- a/core/os/pipe_posix.odin +++ b/core/os/pipe_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "core:sys/posix" import "core:strings" diff --git a/core/os/pipe_wasi.odin b/core/os/pipe_wasi.odin index 19c11b51d..e27c9419e 100644 --- a/core/os/pipe_wasi.odin +++ b/core/os/pipe_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os _pipe :: proc() -> (r, w: ^File, err: Error) { err = .Unsupported diff --git a/core/os/pipe_windows.odin b/core/os/pipe_windows.odin index d6dc47c9c..4e627e26a 100644 --- a/core/os/pipe_windows.odin +++ b/core/os/pipe_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import win32 "core:sys/windows" diff --git a/core/os/process.odin b/core/os/process.odin index e4fecf2a5..9cdaf0457 100644 --- a/core/os/process.odin +++ b/core/os/process.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/process_freebsd.odin b/core/os/process_freebsd.odin index 8a31eb62c..ccc2549db 100644 --- a/core/os/process_freebsd.odin +++ b/core/os/process_freebsd.odin @@ -1,6 +1,6 @@ #+private #+build freebsd -package os2 +package os import "core:c" diff --git a/core/os/process_js.odin b/core/os/process_js.odin index a59a79d45..6283d270c 100644 --- a/core/os/process_js.odin +++ b/core/os/process_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" import "core:time" diff --git a/core/os/process_linux.odin b/core/os/process_linux.odin index 4afd9f3fc..cfdb6774a 100644 --- a/core/os/process_linux.odin +++ b/core/os/process_linux.odin @@ -1,6 +1,6 @@ #+build linux #+private file -package os2 +package os import "base:runtime" import "base:intrinsics" diff --git a/core/os/process_netbsd.odin b/core/os/process_netbsd.odin index b46a58e58..45ca03178 100644 --- a/core/os/process_netbsd.odin +++ b/core/os/process_netbsd.odin @@ -1,6 +1,6 @@ #+private #+build netbsd -package os2 +package os import "core:c" foreign import libc "system:c" diff --git a/core/os/process_openbsd.odin b/core/os/process_openbsd.odin index 9c6605952..5195261ff 100644 --- a/core/os/process_openbsd.odin +++ b/core/os/process_openbsd.odin @@ -1,6 +1,6 @@ #+private #+build openbsd -package os2 +package os import "core:c" diff --git a/core/os/process_posix.odin b/core/os/process_posix.odin index a48e44900..d3ec543f4 100644 --- a/core/os/process_posix.odin +++ b/core/os/process_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/process_posix_darwin.odin b/core/os/process_posix_darwin.odin index 934d23711..a75b4ef96 100644 --- a/core/os/process_posix_darwin.odin +++ b/core/os/process_posix_darwin.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "base:intrinsics" diff --git a/core/os/process_posix_other.odin b/core/os/process_posix_other.odin index 65da3e9e2..85ed6cdd8 100644 --- a/core/os/process_posix_other.odin +++ b/core/os/process_posix_other.odin @@ -1,6 +1,6 @@ #+private #+build netbsd, openbsd, freebsd -package os2 +package os import "base:runtime" diff --git a/core/os/process_wasi.odin b/core/os/process_wasi.odin index efb2c0228..e18fc0524 100644 --- a/core/os/process_wasi.odin +++ b/core/os/process_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/process_windows.odin b/core/os/process_windows.odin index b2c87c4f4..e6db5b4e9 100644 --- a/core/os/process_windows.odin +++ b/core/os/process_windows.odin @@ -1,5 +1,5 @@ #+private file -package os2 +package os import "base:intrinsics" import "base:runtime" diff --git a/core/os/stat.odin b/core/os/stat.odin index 0a9ac4e57..fa92e8a8e 100644 --- a/core/os/stat.odin +++ b/core/os/stat.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" import "core:strings" diff --git a/core/os/stat_js.odin b/core/os/stat_js.odin index e37864936..bad75486b 100644 --- a/core/os/stat_js.odin +++ b/core/os/stat_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/stat_linux.odin b/core/os/stat_linux.odin index dc5bccb54..082279e8d 100644 --- a/core/os/stat_linux.odin +++ b/core/os/stat_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "core:time" import "base:runtime" diff --git a/core/os/stat_posix.odin b/core/os/stat_posix.odin index e401ffe40..924860744 100644 --- a/core/os/stat_posix.odin +++ b/core/os/stat_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/stat_wasi.odin b/core/os/stat_wasi.odin index f15479e22..5f5e6fe45 100644 --- a/core/os/stat_wasi.odin +++ b/core/os/stat_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 651029ac3..51bd57d5b 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import "core:time" diff --git a/core/os/temp_file.odin b/core/os/temp_file.odin index 2c0236428..be10eb22e 100644 --- a/core/os/temp_file.odin +++ b/core/os/temp_file.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/temp_file_js.odin b/core/os/temp_file_js.odin index e1f2b3d95..32ce1c484 100644 --- a/core/os/temp_file_js.odin +++ b/core/os/temp_file_js.odin @@ -1,6 +1,6 @@ #+build js wasm32, js wasm64p32 #+private -package os2 +package os import "base:runtime" diff --git a/core/os/temp_file_linux.odin b/core/os/temp_file_linux.odin index 310720cbe..30f2169ad 100644 --- a/core/os/temp_file_linux.odin +++ b/core/os/temp_file_linux.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/temp_file_posix.odin b/core/os/temp_file_posix.odin index b44ea13a7..a72dc2fab 100644 --- a/core/os/temp_file_posix.odin +++ b/core/os/temp_file_posix.odin @@ -1,6 +1,6 @@ #+private #+build darwin, netbsd, freebsd, openbsd -package os2 +package os import "base:runtime" diff --git a/core/os/temp_file_wasi.odin b/core/os/temp_file_wasi.odin index d5628d300..d3fa941f7 100644 --- a/core/os/temp_file_wasi.odin +++ b/core/os/temp_file_wasi.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" diff --git a/core/os/temp_file_windows.odin b/core/os/temp_file_windows.odin index 91ea284a1..4c927134d 100644 --- a/core/os/temp_file_windows.odin +++ b/core/os/temp_file_windows.odin @@ -1,5 +1,5 @@ #+private -package os2 +package os import "base:runtime" import win32 "core:sys/windows" diff --git a/core/os/user.odin b/core/os/user.odin index e2a4ec4d0..8e952477d 100644 --- a/core/os/user.odin +++ b/core/os/user.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" diff --git a/core/os/user_posix.odin b/core/os/user_posix.odin index fa173f129..355a2e3cd 100644 --- a/core/os/user_posix.odin +++ b/core/os/user_posix.odin @@ -1,5 +1,5 @@ #+build !windows -package os2 +package os import "base:intrinsics" import "base:runtime" diff --git a/core/os/user_windows.odin b/core/os/user_windows.odin index 75d0ba6ac..6f7b74d6d 100644 --- a/core/os/user_windows.odin +++ b/core/os/user_windows.odin @@ -1,4 +1,4 @@ -package os2 +package os import "base:runtime" @(require) import win32 "core:sys/windows" diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin index 301e7bb94..728771b1b 100644 --- a/tests/core/io/test_core_io.odin +++ b/tests/core/io/test_core_io.odin @@ -539,7 +539,7 @@ test_string_builder_stream :: proc(t: ^testing.T) { } @test -test_os2_file_stream :: proc(t: ^testing.T) { +test_os_file_stream :: proc(t: ^testing.T) { defer if !testing.failed(t) { testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) } @@ -549,7 +549,7 @@ test_os2_file_stream :: proc(t: ^testing.T) { buf[i] = 'A' + i } - TEMPORARY_FILENAME :: "test_core_io_os2_file_stream" + TEMPORARY_FILENAME :: "test_core_io_os_file_stream" fd, open_err := os.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc}) if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) { @@ -570,7 +570,7 @@ test_os2_file_stream :: proc(t: ^testing.T) { return } - // os2 file stream proc close and destroy are the same. + // os file stream proc close and destroy are the same. results, _ := _test_stream(t, stream, buf[:], do_destroy = false) log.debugf("%#v", results) @@ -638,7 +638,7 @@ test_bufio_buffered_reader :: proc(t: ^testing.T) { @test test_bufio_buffered_read_writer :: proc(t: ^testing.T) { - // Using an os2.File as the backing stream for both reader & writer. + // Using an os.File as the backing stream for both reader & writer. defer if !testing.failed(t) { testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) @@ -649,7 +649,7 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) { buf[i] = 'A' + i } - TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os2_file_stream" + TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os_file_stream" fd, open_err := os.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc}) if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) { @@ -671,7 +671,7 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) { return } - // bufio.Read_Writer isn't capable of seeking, so we have to reset the os2 + // bufio.Read_Writer isn't capable of seeking, so we have to reset the os // stream back to the start here. pos, seek_err := io.seek(stream, 0, .Start) if !testing.expectf(t, pos == 0 && seek_err == nil, From 7a7127aa13e86c29f2783e79732332edad8c514e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 16:34:24 +0100 Subject: [PATCH 55/59] Fix package line --- core/os/old/dir_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/old/dir_unix.odin b/core/os/old/dir_unix.odin index 6d2a44626..95cc887d6 100644 --- a/core/os/old/dir_unix.odin +++ b/core/os/old/dir_unix.odin @@ -1,5 +1,5 @@ #+build darwin, linux, netbsd, freebsd, openbsd, haiku -package os_old_old +package os_old import "core:strings" From ec05bb57dcde41428b27003dec674dcce87446af Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 18:16:16 +0100 Subject: [PATCH 56/59] Implement `get_processor_core_count` without libc for Linux. --- core/os/process_linux.odin | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/os/process_linux.odin b/core/os/process_linux.odin index cfdb6774a..7f5608a51 100644 --- a/core/os/process_linux.odin +++ b/core/os/process_linux.odin @@ -57,8 +57,15 @@ _get_current_thread_id :: proc "contextless" () -> int { } @(private="package") -_get_processor_core_count :: proc() -> int { - return int(_unix_get_nprocs()) +_get_processor_core_count :: proc() -> (core_count: int) { + cpu_set: [128]u64 + + if err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err != linux.Errno(-1) { + for set in cpu_set { + core_count += int(intrinsics.count_ones(set)) + } + } + return } @(private="package") From fa44c64c42c6f627f27c9b42cf2d79061d483946 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 18:29:31 +0100 Subject: [PATCH 57/59] != nil --- core/os/process_linux.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/process_linux.odin b/core/os/process_linux.odin index 7f5608a51..0f387f01f 100644 --- a/core/os/process_linux.odin +++ b/core/os/process_linux.odin @@ -60,7 +60,7 @@ _get_current_thread_id :: proc "contextless" () -> int { _get_processor_core_count :: proc() -> (core_count: int) { cpu_set: [128]u64 - if err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err != linux.Errno(-1) { + if err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err != nil { for set in cpu_set { core_count += int(intrinsics.count_ones(set)) } From 454ad1f4b82abd781e44bf10e149d8f1ea468a79 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 19:01:43 +0100 Subject: [PATCH 58/59] Unwrap cpu affinity syscalls. --- core/os/process_linux.odin | 2 +- core/sys/linux/sys.odin | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/os/process_linux.odin b/core/os/process_linux.odin index 0f387f01f..1d14c4b1f 100644 --- a/core/os/process_linux.odin +++ b/core/os/process_linux.odin @@ -60,7 +60,7 @@ _get_current_thread_id :: proc "contextless" () -> int { _get_processor_core_count :: proc() -> (core_count: int) { cpu_set: [128]u64 - if err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err != nil { + if _, err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err == nil { for set in cpu_set { core_count += int(intrinsics.count_ones(set)) } diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 392761e1d..6e9c8ab91 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -2616,9 +2616,9 @@ futex :: proc{ If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument. Otherwise use an array of integers. */ -sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) { +sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (int, Errno) { ret := syscall(SYS_sched_setaffinity, pid, cpusetsize, mask) - return Errno(-ret) + return errno_unwrap(ret, int) } /* @@ -2628,9 +2628,9 @@ sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawpt If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument. Otherwise use an array of integers. */ -sched_getaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) { +sched_getaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (int, Errno) { ret := syscall(SYS_sched_getaffinity, pid, cpusetsize, mask) - return Errno(-ret) + return errno_unwrap(ret, int) } // TODO(flysand): set_thread_area From 1a37f4eb0cc792783784fca216c79e3f02a3234e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 19:13:32 +0100 Subject: [PATCH 59/59] Only count bits in touched array members. --- core/os/process_linux.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/os/process_linux.odin b/core/os/process_linux.odin index 1d14c4b1f..7041e16b7 100644 --- a/core/os/process_linux.odin +++ b/core/os/process_linux.odin @@ -59,9 +59,8 @@ _get_current_thread_id :: proc "contextless" () -> int { @(private="package") _get_processor_core_count :: proc() -> (core_count: int) { cpu_set: [128]u64 - - if _, err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err == nil { - for set in cpu_set { + if n, err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err == nil { + for set in cpu_set[:n / 8] { core_count += int(intrinsics.count_ones(set)) } }