From ec314c8324469e188d4568029cba8950377fabc5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:42:17 +0100 Subject: 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 -- cgit v1.2.3 From 3303d3c98f54164ef9a2130c12f12ea6980cea88 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Oct 2025 23:08:56 +0100 Subject: 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 -- cgit v1.2.3 From 304f22c8af635124357c5f62772f6f09676aa761 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:16:42 +0100 Subject: `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" -- cgit v1.2.3 From 1cbd60f40e974eead2b46bc5fdd732c6357dfdfb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:31:54 +0100 Subject: 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" -- cgit v1.2.3 From f63c2094784849ab1ebe1688558e90410af83152 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 00:40:07 +0100 Subject: 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 -- cgit v1.2.3 From 170e314f839f013fe58e0c91a61a34c1dbcfecd2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 01:42:45 +0100 Subject: 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 | 36 ++-- 8 files changed, 62 insertions(+), 417 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,37 +1,34 @@ #+build !js package qoi -import "core:os" -import "core:bytes" - -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 - - out := &bytes.Buffer{} - defer bytes.buffer_destroy(out) - - save_to_buffer(out, img, options) or_return - write_ok := os.write_entire_file(output, out.buf[:]) - - return nil if write_ok else .Unable_To_Write_File -} - +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 } +} + +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 + + out := &bytes.Buffer{} + defer bytes.buffer_destroy(out) + + save_to_buffer(out, img, options) or_return + write_err := os.write_entire_file(output, out.buf[:]) + + 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,34 +1,34 @@ #+build !js package tga -import "core:os" -import "core:bytes" - -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 - - out := &bytes.Buffer{} - defer bytes.buffer_destroy(out) - - save_to_buffer(out, img, options) or_return - write_ok := os.write_entire_file(output, out.buf[:]) - - return nil if write_ok else .Unable_To_Write_File -} +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) + data, data_err := os.read_entire_file(filename, allocator) defer delete(data) - if ok { + 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 + + out := &bytes.Buffer{} + defer bytes.buffer_destroy(out) + + save_to_buffer(out, img, options) or_return + write_err := os.write_entire_file(output, out.buf[:]) + + return nil if write_err == nil else .Unable_To_Write_File } \ No newline at end of file -- cgit v1.2.3 From 9c1a9c80f8cd16b7ad57ca672eccf8f020cecbb5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 02:07:03 +0100 Subject: 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 } /* -- cgit v1.2.3 From a86ec959441dc9433074dba03373f53b6f05622a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 02:39:19 +0100 Subject: 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 -- cgit v1.2.3 From 4eab15130f1462f564f13f9a98ea54545088b411 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:43:55 +0100 Subject: 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 /* -- cgit v1.2.3 From e094de58746e2d982b6a23e28c5db2870b45d43d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 11:13:34 +0100 Subject: 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) } /* -- cgit v1.2.3 From 02477b25264659f10889ce84e57a6603d9d613a2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 11:16:56 +0100 Subject: 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 */ } -- cgit v1.2.3 From 456f9b17edd7104ebd6d938aa565b59023be7f2d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 14:20:28 +0100 Subject: `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 } -- cgit v1.2.3 From 47786deddc32f00243779995ff3a9e8f52713cf2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:01:25 +0100 Subject: 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 -- cgit v1.2.3 From 14021f6444248a80fdda6fb5a4e002ab94e4d9a7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:34:13 +0100 Subject: 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 { -- cgit v1.2.3 From ab0f1aa0c43c4b71ef1b57f9b53b1e9ec16878bb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 28 Oct 2025 19:40:04 +0100 Subject: 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 { -- cgit v1.2.3 From 586355f4ac7f308b8a304078fbb15191ffbba952 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 11:29:20 +0100 Subject: 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) } -- cgit v1.2.3 From a4970191724182a14a4000c60c9a0677b7fa570d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:11:14 +0100 Subject: 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) { -- cgit v1.2.3 From c0da9be4a9e216d1c050c8b5c000a34f87c5a004 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:18:12 +0100 Subject: 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 { -- cgit v1.2.3 From 5d8de5860ba6b1234facfcd5db2d83db9a551f07 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 14:40:15 +0100 Subject: 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) { -- cgit v1.2.3 From c2647673ec503938a302bd083f42602a5a000177 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 Oct 2025 15:43:31 +0100 Subject: 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 -- cgit v1.2.3 From 8f4bcf4d31fe83f2fe9a93a0b0a8e28d11c0c882 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:48:41 +0100 Subject: 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. -- cgit v1.2.3 From ffa94764b44763dddb32e069c92b6113b506e639 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 12:29:23 +0100 Subject: 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.File @(private="file") -_write_pipe: os.Handle +_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) } -- cgit v1.2.3 From 67db0fde4fbfaf34870b5f5ac3042c50df9a0c2c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 12:38:28 +0100 Subject: 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 { -- cgit v1.2.3 From c265d297b5c9252db391cdf43eca079d8f634791 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 13:45:59 +0100 Subject: 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) } -- cgit v1.2.3 From 8b3ae667f449f7f013d926b5f3d9177b2483e54f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:50:19 +0100 Subject: 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 -- cgit v1.2.3 From af57035fd65cda96b7ec17e965184a922fdf6e82 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 15:07:51 +0100 Subject: 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 @@ -539,47 +538,10 @@ test_string_builder_stream :: proc(t: ^testing.T) { log.debugf("%#v", results) } -@test -test_os_file_stream :: proc(t: ^testing.T) { - defer if !testing.failed(t) { - testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) - } - - buf: [32]u8 - for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) { - return - } - - flush_err := io.flush(stream) - if !testing.expectf(t, flush_err == nil, - "failed to Flush initial buffer: %v", write_err) { - return - } - - results, _ := _test_stream(t, stream, buf[:]) - - log.debugf("%#v", results) -} - @test test_os2_file_stream :: proc(t: ^testing.T) { defer if !testing.failed(t) { - testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil) + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) } buf: [32]u8 @@ -589,12 +551,12 @@ test_os2_file_stream :: proc(t: ^testing.T) { TEMPORARY_FILENAME :: "test_core_io_os2_file_stream" - fd, open_err := os2.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc}) + 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) { return } - stream := os2.to_stream(fd) + stream := os.to_stream(fd) bytes_written, write_err := io.write(stream, buf[:]) if !testing.expectf(t, bytes_written == len(buf) && write_err == nil, @@ -679,7 +641,7 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) { // Using an os2.File as the backing stream for both reader & writer. defer if !testing.failed(t) { - testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil) + testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil) } buf: [32]u8 @@ -689,13 +651,13 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) { TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os2_file_stream" - fd, open_err := os2.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc}) + 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) { return } - defer testing.expect_value(t, os2.close(fd), nil) + defer testing.expect_value(t, os.close(fd), nil) - stream := os2.to_stream(fd) + stream := os.to_stream(fd) bytes_written, write_err := io.write(stream, buf[:]) if !testing.expectf(t, bytes_written == len(buf) && write_err == nil, -- cgit v1.2.3 From af8bc8bbfc8939234242690bc74c1ffe075a15df Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:51:24 +0100 Subject: 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 } } -- cgit v1.2.3 From cc50be1a6cea7587656124ad3fc0917e1ad9f737 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Oct 2025 23:56:13 +0100 Subject: 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 -- cgit v1.2.3 From 691dc44719700add54f1ec86fbed9f9a93183855 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 Oct 2025 14:24:30 +0100 Subject: 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 -- cgit v1.2.3 From 2f587767453eb486df7395be93554e9b5029be4c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:54:20 +0100 Subject: 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 } -- cgit v1.2.3 From 1c9e36b05ee5d0ad0fd867d71ccc6863ebc90e79 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 11:01:30 +0100 Subject: 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 } -- cgit v1.2.3 From 5d03da8365bd17715a29e497398633fcefcbd8c7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 11:24:39 +0100 Subject: 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) { } } -@(test) -test_glob :: 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 -- cgit v1.2.3 From 5924fb448ee13deca0fe6536cdd7f76fe5d6a583 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 12:59:35 +0100 Subject: 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 -- cgit v1.2.3 From 0c341123cb7cd07db56def2be23865550bdef75a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:56:00 +0100 Subject: 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 -- cgit v1.2.3 From 2e970db51dc6e9edaefd6e3346807d6974bd155d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 17:19:46 +0100 Subject: 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 | 160 ++++++++++++++++++++--------------- 9 files changed, 143 insertions(+), 275 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) - -// 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) - - 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 -} +Walker :: os.Walker -@(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) - } +/* +Initializes 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) +You are allowed to repeatedly call this to reuse it for later walks. - err1, skip_dir = walk_proc(info, err, user_data) - if err != nil || err1 != nil || skip_dir { - err = err1 - return - } +For an example on how to use the walker, see `walker_walk`. +*/ +walker_init :: os.walker_init + +/* +Creates a walker, either using a path or a file pointer to a directory the walker will start at. + +For an example on how to use the walker, see `walker_walk`. +*/ +walker_create :: os.walker_create + +/* +Returns the last error that occurred during the walker's operations. + +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" - 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 + 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 + +/* + 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 + +/* + 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 - return -} - -@(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 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 -- cgit v1.2.3 From 062d7ae3858f1e66ba1cc36574c2c5c2d337150a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 17:56:26 +0100 Subject: 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) -- cgit v1.2.3 From 2efefaac1add693fb49c11513ab963d5c30e2c30 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Nov 2025 18:21:34 +0100 Subject: 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 -- cgit v1.2.3 From 13228c14e86d357ceac616335526d805e3965940 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:04:44 +0100 Subject: More conflicts during rebase --- core/flags/errors.odin | 6 +-- core/flags/example/example.odin | 8 ++-- core/flags/internal_rtti.odin | 56 ++++++++++++------------ 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 ++--- tests/core/path/filepath/test_core_filepath.odin | 12 ++--- 8 files changed, 71 insertions(+), 66 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 { -- cgit v1.2.3 From 5644db99f08ea9a4b59be64b7cc6c96cda870ebc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 11:30:57 +0100 Subject: 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 -- cgit v1.2.3 From 2c39af1581241e3e7c61d4d1f84aaecaceb712b8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 11:47:27 +0100 Subject: 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 -- cgit v1.2.3 From 940105dc19f70c5ed7efc0d8957b76461616b7fa Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 14:40:17 +0100 Subject: Convert fmt_js.odin to not use `core:os*` --- core/fmt/fmt_js.odin | 107 ++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 61 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)), -} +BUF_SIZE :: 1024 + @(private="file") -stderr := io.Writer{ - procedure = write_stream_proc, - data = rawptr(uintptr(2)), -} +// TODO: Find a way to grow this if necessary +buf: [BUF_SIZE]byte @(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) +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 -- cgit v1.2.3 From 876935065f51b1bb5557cd0e7604d41b0fb9e42f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Nov 2025 14:50:33 +0100 Subject: 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") -- cgit v1.2.3 From 6270b02b2c40e5f7469573ff4fecc1b092106e97 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 3 Nov 2025 10:58:09 +0100 Subject: 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 -- cgit v1.2.3 From fb7ac1fb648e567d04626389b8113997fec4cae2 Mon Sep 17 00:00:00 2001 From: Laytan Date: Mon, 3 Nov 2025 19:27:02 +0100 Subject: 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) + } +} -- cgit v1.2.3 From 470a245f5fc927fa9d04da0adcbcd57574d4067f Mon Sep 17 00:00:00 2001 From: Laytan Date: Mon, 3 Nov 2025 19:51:42 +0100 Subject: 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) -- cgit v1.2.3 From 38def33c95bec05ad5687fa074d7e26f2b1f923b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 12:40:51 +0100 Subject: 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) -- cgit v1.2.3 From 89fccd6056a4ff188f82ff8364aedcbf5b832bb1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:17:39 +0100 Subject: 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 { -- cgit v1.2.3 From 29b6a7ddfe39dce2b932066a12c3e69d809b7965 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:21:01 +0100 Subject: 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" -- cgit v1.2.3 From 674f0e04745d0f679ce540ec4ae025910a612757 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:28:04 +0100 Subject: 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) -- cgit v1.2.3 From b16ca5a986c129500285c238bbe1d3e0dd44e75e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 13:51:49 +0100 Subject: fix libc-shim --- vendor/libc-shim/stdio_js.odin | 60 ++++++++++++++++++++++++ vendor/libc-shim/stdio_os.odin | 104 +++++++++++++++++++++++++++++++++++++++++ vendor/libc/stdio_js.odin | 60 ------------------------ vendor/libc/stdio_os.odin | 104 ----------------------------------------- 4 files changed, 164 insertions(+), 164 deletions(-) create mode 100644 vendor/libc-shim/stdio_js.odin create mode 100644 vendor/libc-shim/stdio_os.odin delete mode 100644 vendor/libc/stdio_js.odin delete mode 100644 vendor/libc/stdio_os.odin diff --git a/vendor/libc-shim/stdio_js.odin b/vendor/libc-shim/stdio_js.odin new file mode 100644 index 000000000..2382ed449 --- /dev/null +++ b/vendor/libc-shim/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-shim/stdio_os.odin b/vendor/libc-shim/stdio_os.odin new file mode 100644 index 000000000..db40fb250 --- /dev/null +++ b/vendor/libc-shim/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) + } +} diff --git a/vendor/libc/stdio_js.odin b/vendor/libc/stdio_js.odin deleted file mode 100644 index 2382ed449..000000000 --- a/vendor/libc/stdio_js.odin +++ /dev/null @@ -1,60 +0,0 @@ -#+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 deleted file mode 100644 index db40fb250..000000000 --- a/vendor/libc/stdio_os.odin +++ /dev/null @@ -1,104 +0,0 @@ -#+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) - } -} -- cgit v1.2.3 From 5bf30b2d562dd7b746a351dce333914e2ca958c8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 14:13:20 +0100 Subject: 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 } -- cgit v1.2.3 From 8ed264680b1f3f94b6aa5176824d4ccadfc30322 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 8 Feb 2026 16:39:56 +0100 Subject: 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 +------- core/terminal/internal.odin | 80 ---------------------------------- core/terminal/internal_os.odin | 82 +++++++++++++++++++++++++++++++++++ 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 +- 32 files changed, 368 insertions(+), 267 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 delete mode 100644 core/terminal/internal.odin create mode 100644 core/terminal/internal_os.odin 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.odin deleted file mode 100644 index 956726b8a..000000000 --- a/core/terminal/internal.odin +++ /dev/null @@ -1,80 +0,0 @@ -#+private -package terminal - -import "base:runtime" -import os "core:os/os2" -import "core:strings" - -// Reference documentation: -// -// - [[ https://no-color.org/ ]] -// - [[ https://github.com/termstandard/colors ]] -// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] - -get_no_color :: proc() -> bool { - buf: [128]u8 - if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil { - return no_color != "" - } - return false -} - -get_environment_color :: proc() -> Color_Depth { - buf: [128]u8 - // `COLORTERM` is non-standard but widespread and unambiguous. - if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil { - // These are the only values that are typically advertised that have - // anything to do with color depth. - if colorterm == "truecolor" || colorterm == "24bit" { - return .True_Color - } - } - - if term, err := os.lookup_env(buf[:], "TERM"); err == nil { - if strings.contains(term, "-truecolor") { - return .True_Color - } - if strings.contains(term, "-256color") { - return .Eight_Bit - } - if strings.contains(term, "-16color") { - return .Four_Bit - } - - // The `terminfo` database, which is stored in binary on *nix - // platforms, has an undocumented format that is not guaranteed to be - // portable, so beyond this point, we can only make safe assumptions. - // - // This section should only be necessary for terminals that do not - // define any of the previous environment values. - // - // Only a small sampling of some common values are checked here. - switch term { - case "ansi", "konsole", "putty", "rxvt", "rxvt-color", "screen", - "st", "tmux", "vte", "xterm", "xterm-color": - return .Three_Bit - } - } - - return .None -} - -@(init) -init_terminal :: proc "contextless" () { - _init_terminal() - - context = runtime.default_context() - - // We respect `NO_COLOR` specifically as a color-disabler but not as a - // blanket ban on any terminal manipulation codes, hence why this comes - // after `_init_terminal` which will allow Windows to enable Virtual - // Terminal Processing for non-color control sequences. - if !get_no_color() { - color_enabled = color_depth > .None - } -} - -@(fini) -fini_terminal :: proc "contextless" () { - _fini_terminal() -} \ No newline at end of file diff --git a/core/terminal/internal_os.odin b/core/terminal/internal_os.odin new file mode 100644 index 000000000..841803766 --- /dev/null +++ b/core/terminal/internal_os.odin @@ -0,0 +1,82 @@ +#+build !freestanding +#+build !js +#+private +package terminal + +import "base:runtime" +import os "core:os/os2" +import "core:strings" + +// Reference documentation: +// +// - [[ https://no-color.org/ ]] +// - [[ https://github.com/termstandard/colors ]] +// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + +get_no_color :: proc() -> bool { + buf: [128]u8 + if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil { + return no_color != "" + } + return false +} + +get_environment_color :: proc() -> Color_Depth { + buf: [128]u8 + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil { + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, err := os.lookup_env(buf[:], "TERM"); err == nil { + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi", "konsole", "putty", "rxvt", "rxvt-color", "screen", + "st", "tmux", "vte", "xterm", "xterm-color": + return .Three_Bit + } + } + + return .None +} + +@(init) +init_terminal :: proc "contextless" () { + _init_terminal() + + context = runtime.default_context() + + // We respect `NO_COLOR` specifically as a color-disabler but not as a + // blanket ban on any terminal manipulation codes, hence why this comes + // after `_init_terminal` which will allow Windows to enable Virtual + // Terminal Processing for non-color control sequences. + if !get_no_color() { + color_enabled = color_depth > .None + } +} + +@(fini) +fini_terminal :: proc "contextless" () { + _fini_terminal() +} \ No newline at end of file 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) -- cgit v1.2.3 From e7dbabf6681e4e6bcae33398e939c2c9c3cdc879 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 15:50:21 +0100 Subject: 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/allocators.odin | 74 ++ core/os/dir.odin | 262 +++++ core/os/dir_js.odin | 24 + core/os/dir_linux.odin | 120 +++ core/os/dir_posix.odin | 104 ++ core/os/dir_posix_darwin.odin | 17 + core/os/dir_unix.odin | 65 -- core/os/dir_walker.odin | 230 +++++ core/os/dir_wasi.odin | 123 +++ core/os/dir_windows.odin | 190 ++-- core/os/doc.odin | 6 + core/os/env.odin | 122 +++ core/os/env_js.odin | 42 + core/os/env_linux.odin | 369 +++++++ core/os/env_posix.odin | 110 ++ core/os/env_wasi.odin | 188 ++++ core/os/env_windows.odin | 142 +-- core/os/errors.odin | 257 +---- core/os/errors_js.odin | 13 + core/os/errors_linux.odin | 177 ++++ core/os/errors_posix.odin | 43 + core/os/errors_wasi.odin | 47 + core/os/errors_windows.odin | 78 ++ core/os/file.odin | 570 +++++++++++ core/os/file_js.odin | 110 ++ core/os/file_linux.odin | 560 ++++++++++ core/os/file_posix.odin | 514 ++++++++++ core/os/file_posix_darwin.odin | 46 + core/os/file_posix_freebsd.odin | 47 + core/os/file_posix_netbsd.odin | 18 + core/os/file_posix_other.odin | 21 + core/os/file_stream.odin | 89 ++ core/os/file_util.odin | 257 +++++ core/os/file_wasi.odin | 570 +++++++++++ core/os/file_windows.odin | 995 ++++++++++++++++++ core/os/heap.odin | 22 + core/os/heap_js.odin | 7 + core/os/heap_linux.odin | 6 + core/os/heap_posix.odin | 7 + core/os/heap_wasi.odin | 6 + core/os/heap_windows.odin | 106 ++ core/os/internal_util.odin | 94 ++ core/os/old/dir_unix.odin | 65 ++ 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 | 266 +++++ core/os/old/os_darwin.odin | 1348 +++++++++++++++++++++++++ core/os/old/os_essence.odin | 60 ++ core/os/old/os_freebsd.odin | 982 ++++++++++++++++++ core/os/old/os_freestanding.odin | 4 + core/os/old/os_haiku.odin | 544 ++++++++++ core/os/old/os_js.odin | 275 +++++ core/os/old/os_linux.odin | 1233 ++++++++++++++++++++++ core/os/old/os_netbsd.odin | 1032 +++++++++++++++++++ core/os/old/os_openbsd.odin | 932 +++++++++++++++++ core/os/old/os_wasi.odin | 273 +++++ core/os/old/os_windows.odin | 871 ++++++++++++++++ core/os/old/stat.odin | 33 + core/os/old/stat_unix.odin | 134 +++ core/os/old/stat_windows.odin | 303 ++++++ core/os/old/stream.odin | 77 ++ core/os/os.odin | 266 ----- core/os/os2/allocators.odin | 74 -- core/os/os2/dir.odin | 262 ----- core/os/os2/dir_js.odin | 24 - core/os/os2/dir_linux.odin | 120 --- core/os/os2/dir_posix.odin | 104 -- core/os/os2/dir_posix_darwin.odin | 17 - core/os/os2/dir_walker.odin | 230 ----- core/os/os2/dir_wasi.odin | 123 --- core/os/os2/dir_windows.odin | 144 --- core/os/os2/doc.odin | 6 - core/os/os2/env.odin | 122 --- core/os/os2/env_js.odin | 42 - core/os/os2/env_linux.odin | 369 ------- core/os/os2/env_posix.odin | 110 -- core/os/os2/env_wasi.odin | 188 ---- core/os/os2/env_windows.odin | 142 --- core/os/os2/errors.odin | 147 --- core/os/os2/errors_js.odin | 13 - core/os/os2/errors_linux.odin | 177 ---- core/os/os2/errors_posix.odin | 43 - core/os/os2/errors_wasi.odin | 47 - core/os/os2/errors_windows.odin | 78 -- core/os/os2/file.odin | 570 ----------- core/os/os2/file_js.odin | 110 -- core/os/os2/file_linux.odin | 560 ---------- core/os/os2/file_posix.odin | 514 ---------- core/os/os2/file_posix_darwin.odin | 46 - core/os/os2/file_posix_freebsd.odin | 47 - core/os/os2/file_posix_netbsd.odin | 18 - core/os/os2/file_posix_other.odin | 21 - core/os/os2/file_stream.odin | 89 -- core/os/os2/file_util.odin | 257 ----- core/os/os2/file_wasi.odin | 570 ----------- core/os/os2/file_windows.odin | 995 ------------------ core/os/os2/heap.odin | 22 - core/os/os2/heap_js.odin | 7 - core/os/os2/heap_linux.odin | 6 - core/os/os2/heap_posix.odin | 7 - core/os/os2/heap_wasi.odin | 6 - core/os/os2/heap_windows.odin | 106 -- core/os/os2/internal_util.odin | 94 -- core/os/os2/path.odin | 980 ------------------ core/os/os2/path_darwin.odin | 17 - core/os/os2/path_freebsd.odin | 29 - core/os/os2/path_js.odin | 85 -- core/os/os2/path_linux.odin | 227 ----- core/os/os2/path_netbsd.odin | 24 - core/os/os2/path_openbsd.odin | 57 -- core/os/os2/path_posix.odin | 142 --- core/os/os2/path_posixfs.odin | 57 -- core/os/os2/path_wasi.odin | 120 --- core/os/os2/path_windows.odin | 359 ------- core/os/os2/pipe.odin | 43 - core/os/os2/pipe_js.odin | 14 - core/os/os2/pipe_linux.odin | 43 - core/os/os2/pipe_posix.odin | 73 -- core/os/os2/pipe_wasi.odin | 13 - core/os/os2/pipe_windows.odin | 29 - core/os/os2/process.odin | 548 ---------- core/os/os2/process_freebsd.odin | 36 - core/os/os2/process_js.odin | 95 -- core/os/os2/process_linux.odin | 868 ---------------- core/os/os2/process_netbsd.odin | 31 - core/os/os2/process_openbsd.odin | 25 - core/os/os2/process_posix.odin | 344 ------- core/os/os2/process_posix_darwin.odin | 332 ------ core/os/os2/process_posix_other.odin | 29 - core/os/os2/process_wasi.odin | 91 -- core/os/os2/process_windows.odin | 799 --------------- core/os/os2/stat.odin | 117 --- core/os/os2/stat_js.odin | 25 - core/os/os2/stat_linux.odin | 79 -- core/os/os2/stat_posix.odin | 141 --- core/os/os2/stat_wasi.odin | 104 -- core/os/os2/stat_windows.odin | 393 ------- core/os/os2/temp_file.odin | 110 -- core/os/os2/temp_file_js.odin | 9 - core/os/os2/temp_file_linux.odin | 13 - core/os/os2/temp_file_posix.odin | 20 - core/os/os2/temp_file_wasi.odin | 9 - core/os/os2/temp_file_windows.odin | 23 - core/os/os2/user.odin | 149 --- core/os/os2/user_posix.odin | 176 ---- core/os/os2/user_windows.odin | 78 -- core/os/os_darwin.odin | 1348 ------------------------- core/os/os_essence.odin | 60 -- core/os/os_freebsd.odin | 982 ------------------ core/os/os_freestanding.odin | 4 - core/os/os_haiku.odin | 544 ---------- core/os/os_js.odin | 275 ----- core/os/os_linux.odin | 1233 ---------------------- core/os/os_netbsd.odin | 1032 ------------------- core/os/os_openbsd.odin | 932 ----------------- core/os/os_wasi.odin | 273 ----- core/os/os_windows.odin | 871 ---------------- core/os/path.odin | 980 ++++++++++++++++++ core/os/path_darwin.odin | 17 + core/os/path_freebsd.odin | 29 + core/os/path_js.odin | 85 ++ core/os/path_linux.odin | 227 +++++ core/os/path_netbsd.odin | 24 + core/os/path_openbsd.odin | 57 ++ core/os/path_posix.odin | 142 +++ core/os/path_posixfs.odin | 57 ++ core/os/path_wasi.odin | 120 +++ core/os/path_windows.odin | 359 +++++++ core/os/pipe.odin | 43 + core/os/pipe_js.odin | 14 + core/os/pipe_linux.odin | 43 + core/os/pipe_posix.odin | 73 ++ core/os/pipe_wasi.odin | 13 + core/os/pipe_windows.odin | 29 + core/os/process.odin | 548 ++++++++++ core/os/process_freebsd.odin | 36 + core/os/process_js.odin | 95 ++ core/os/process_linux.odin | 868 ++++++++++++++++ core/os/process_netbsd.odin | 31 + core/os/process_openbsd.odin | 25 + core/os/process_posix.odin | 344 +++++++ core/os/process_posix_darwin.odin | 332 ++++++ core/os/process_posix_other.odin | 29 + core/os/process_wasi.odin | 91 ++ core/os/process_windows.odin | 799 +++++++++++++++ core/os/stat.odin | 116 ++- core/os/stat_js.odin | 25 + core/os/stat_linux.odin | 79 ++ core/os/stat_posix.odin | 141 +++ core/os/stat_unix.odin | 134 --- core/os/stat_wasi.odin | 104 ++ core/os/stat_windows.odin | 422 +++++--- core/os/stream.odin | 77 -- core/os/temp_file.odin | 110 ++ core/os/temp_file_js.odin | 9 + core/os/temp_file_linux.odin | 13 + core/os/temp_file_posix.odin | 20 + core/os/temp_file_wasi.odin | 9 + core/os/temp_file_windows.odin | 23 + core/os/user.odin | 149 +++ core/os/user_posix.odin | 176 ++++ core/os/user_windows.odin | 78 ++ 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/dir.odin | 116 +++ tests/core/os/file.odin | 33 + tests/core/os/old/os.odin | 63 ++ tests/core/os/os.odin | 63 -- tests/core/os/os2/dir.odin | 116 --- tests/core/os/os2/file.odin | 33 - tests/core/os/os2/path.odin | 562 ----------- tests/core/os/os2/process.odin | 26 - tests/core/os/path.odin | 562 +++++++++++ tests/core/os/process.odin | 26 + 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 +- 268 files changed, 23197 insertions(+), 23199 deletions(-) create mode 100644 core/os/allocators.odin create mode 100644 core/os/dir.odin create mode 100644 core/os/dir_js.odin create mode 100644 core/os/dir_linux.odin create mode 100644 core/os/dir_posix.odin create mode 100644 core/os/dir_posix_darwin.odin delete mode 100644 core/os/dir_unix.odin create mode 100644 core/os/dir_walker.odin create mode 100644 core/os/dir_wasi.odin create mode 100644 core/os/doc.odin create mode 100644 core/os/env.odin create mode 100644 core/os/env_js.odin create mode 100644 core/os/env_linux.odin create mode 100644 core/os/env_posix.odin create mode 100644 core/os/env_wasi.odin create mode 100644 core/os/errors_js.odin create mode 100644 core/os/errors_linux.odin create mode 100644 core/os/errors_posix.odin create mode 100644 core/os/errors_wasi.odin create mode 100644 core/os/errors_windows.odin create mode 100644 core/os/file.odin create mode 100644 core/os/file_js.odin create mode 100644 core/os/file_linux.odin create mode 100644 core/os/file_posix.odin create mode 100644 core/os/file_posix_darwin.odin create mode 100644 core/os/file_posix_freebsd.odin create mode 100644 core/os/file_posix_netbsd.odin create mode 100644 core/os/file_posix_other.odin create mode 100644 core/os/file_stream.odin create mode 100644 core/os/file_util.odin create mode 100644 core/os/file_wasi.odin create mode 100644 core/os/file_windows.odin create mode 100644 core/os/heap.odin create mode 100644 core/os/heap_js.odin create mode 100644 core/os/heap_linux.odin create mode 100644 core/os/heap_posix.odin create mode 100644 core/os/heap_wasi.odin create mode 100644 core/os/heap_windows.odin create mode 100644 core/os/internal_util.odin create mode 100644 core/os/old/dir_unix.odin 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 create mode 100644 core/os/old/os.odin create mode 100644 core/os/old/os_darwin.odin create mode 100644 core/os/old/os_essence.odin create mode 100644 core/os/old/os_freebsd.odin create mode 100644 core/os/old/os_freestanding.odin create mode 100644 core/os/old/os_haiku.odin create mode 100644 core/os/old/os_js.odin create mode 100644 core/os/old/os_linux.odin create mode 100644 core/os/old/os_netbsd.odin create mode 100644 core/os/old/os_openbsd.odin create mode 100644 core/os/old/os_wasi.odin create mode 100644 core/os/old/os_windows.odin create mode 100644 core/os/old/stat.odin create mode 100644 core/os/old/stat_unix.odin create mode 100644 core/os/old/stat_windows.odin create mode 100644 core/os/old/stream.odin delete mode 100644 core/os/os.odin delete mode 100644 core/os/os2/allocators.odin delete mode 100644 core/os/os2/dir.odin delete mode 100644 core/os/os2/dir_js.odin delete mode 100644 core/os/os2/dir_linux.odin delete mode 100644 core/os/os2/dir_posix.odin delete mode 100644 core/os/os2/dir_posix_darwin.odin delete mode 100644 core/os/os2/dir_walker.odin delete mode 100644 core/os/os2/dir_wasi.odin delete mode 100644 core/os/os2/dir_windows.odin delete mode 100644 core/os/os2/doc.odin delete mode 100644 core/os/os2/env.odin delete mode 100644 core/os/os2/env_js.odin delete mode 100644 core/os/os2/env_linux.odin delete mode 100644 core/os/os2/env_posix.odin delete mode 100644 core/os/os2/env_wasi.odin delete mode 100644 core/os/os2/env_windows.odin delete mode 100644 core/os/os2/errors.odin delete mode 100644 core/os/os2/errors_js.odin delete mode 100644 core/os/os2/errors_linux.odin delete mode 100644 core/os/os2/errors_posix.odin delete mode 100644 core/os/os2/errors_wasi.odin delete mode 100644 core/os/os2/errors_windows.odin delete mode 100644 core/os/os2/file.odin delete mode 100644 core/os/os2/file_js.odin delete mode 100644 core/os/os2/file_linux.odin delete mode 100644 core/os/os2/file_posix.odin delete mode 100644 core/os/os2/file_posix_darwin.odin delete mode 100644 core/os/os2/file_posix_freebsd.odin delete mode 100644 core/os/os2/file_posix_netbsd.odin delete mode 100644 core/os/os2/file_posix_other.odin delete mode 100644 core/os/os2/file_stream.odin delete mode 100644 core/os/os2/file_util.odin delete mode 100644 core/os/os2/file_wasi.odin delete mode 100644 core/os/os2/file_windows.odin delete mode 100644 core/os/os2/heap.odin delete mode 100644 core/os/os2/heap_js.odin delete mode 100644 core/os/os2/heap_linux.odin delete mode 100644 core/os/os2/heap_posix.odin delete mode 100644 core/os/os2/heap_wasi.odin delete mode 100644 core/os/os2/heap_windows.odin delete mode 100644 core/os/os2/internal_util.odin delete mode 100644 core/os/os2/path.odin delete mode 100644 core/os/os2/path_darwin.odin delete mode 100644 core/os/os2/path_freebsd.odin delete mode 100644 core/os/os2/path_js.odin delete mode 100644 core/os/os2/path_linux.odin delete mode 100644 core/os/os2/path_netbsd.odin delete mode 100644 core/os/os2/path_openbsd.odin delete mode 100644 core/os/os2/path_posix.odin delete mode 100644 core/os/os2/path_posixfs.odin delete mode 100644 core/os/os2/path_wasi.odin delete mode 100644 core/os/os2/path_windows.odin delete mode 100644 core/os/os2/pipe.odin delete mode 100644 core/os/os2/pipe_js.odin delete mode 100644 core/os/os2/pipe_linux.odin delete mode 100644 core/os/os2/pipe_posix.odin delete mode 100644 core/os/os2/pipe_wasi.odin delete mode 100644 core/os/os2/pipe_windows.odin delete mode 100644 core/os/os2/process.odin delete mode 100644 core/os/os2/process_freebsd.odin delete mode 100644 core/os/os2/process_js.odin delete mode 100644 core/os/os2/process_linux.odin delete mode 100644 core/os/os2/process_netbsd.odin delete mode 100644 core/os/os2/process_openbsd.odin delete mode 100644 core/os/os2/process_posix.odin delete mode 100644 core/os/os2/process_posix_darwin.odin delete mode 100644 core/os/os2/process_posix_other.odin delete mode 100644 core/os/os2/process_wasi.odin delete mode 100644 core/os/os2/process_windows.odin delete mode 100644 core/os/os2/stat.odin delete mode 100644 core/os/os2/stat_js.odin delete mode 100644 core/os/os2/stat_linux.odin delete mode 100644 core/os/os2/stat_posix.odin delete mode 100644 core/os/os2/stat_wasi.odin delete mode 100644 core/os/os2/stat_windows.odin delete mode 100644 core/os/os2/temp_file.odin delete mode 100644 core/os/os2/temp_file_js.odin delete mode 100644 core/os/os2/temp_file_linux.odin delete mode 100644 core/os/os2/temp_file_posix.odin delete mode 100644 core/os/os2/temp_file_wasi.odin delete mode 100644 core/os/os2/temp_file_windows.odin delete mode 100644 core/os/os2/user.odin delete mode 100644 core/os/os2/user_posix.odin delete mode 100644 core/os/os2/user_windows.odin delete mode 100644 core/os/os_darwin.odin delete mode 100644 core/os/os_essence.odin delete mode 100644 core/os/os_freebsd.odin delete mode 100644 core/os/os_freestanding.odin delete mode 100644 core/os/os_haiku.odin delete mode 100644 core/os/os_js.odin delete mode 100644 core/os/os_linux.odin delete mode 100644 core/os/os_netbsd.odin delete mode 100644 core/os/os_openbsd.odin delete mode 100644 core/os/os_wasi.odin delete mode 100644 core/os/os_windows.odin create mode 100644 core/os/path.odin create mode 100644 core/os/path_darwin.odin create mode 100644 core/os/path_freebsd.odin create mode 100644 core/os/path_js.odin create mode 100644 core/os/path_linux.odin create mode 100644 core/os/path_netbsd.odin create mode 100644 core/os/path_openbsd.odin create mode 100644 core/os/path_posix.odin create mode 100644 core/os/path_posixfs.odin create mode 100644 core/os/path_wasi.odin create mode 100644 core/os/path_windows.odin create mode 100644 core/os/pipe.odin create mode 100644 core/os/pipe_js.odin create mode 100644 core/os/pipe_linux.odin create mode 100644 core/os/pipe_posix.odin create mode 100644 core/os/pipe_wasi.odin create mode 100644 core/os/pipe_windows.odin create mode 100644 core/os/process.odin create mode 100644 core/os/process_freebsd.odin create mode 100644 core/os/process_js.odin create mode 100644 core/os/process_linux.odin create mode 100644 core/os/process_netbsd.odin create mode 100644 core/os/process_openbsd.odin create mode 100644 core/os/process_posix.odin create mode 100644 core/os/process_posix_darwin.odin create mode 100644 core/os/process_posix_other.odin create mode 100644 core/os/process_wasi.odin create mode 100644 core/os/process_windows.odin create mode 100644 core/os/stat_js.odin create mode 100644 core/os/stat_linux.odin create mode 100644 core/os/stat_posix.odin delete mode 100644 core/os/stat_unix.odin create mode 100644 core/os/stat_wasi.odin delete mode 100644 core/os/stream.odin create mode 100644 core/os/temp_file.odin create mode 100644 core/os/temp_file_js.odin create mode 100644 core/os/temp_file_linux.odin create mode 100644 core/os/temp_file_posix.odin create mode 100644 core/os/temp_file_wasi.odin create mode 100644 core/os/temp_file_windows.odin create mode 100644 core/os/user.odin create mode 100644 core/os/user_posix.odin create mode 100644 core/os/user_windows.odin create mode 100644 tests/core/os/dir.odin create mode 100644 tests/core/os/file.odin create mode 100644 tests/core/os/old/os.odin delete mode 100644 tests/core/os/os.odin delete mode 100644 tests/core/os/os2/dir.odin delete mode 100644 tests/core/os/os2/file.odin delete mode 100644 tests/core/os/os2/path.odin delete mode 100644 tests/core/os/os2/process.odin create mode 100644 tests/core/os/path.odin create mode 100644 tests/core/os/process.odin 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/allocators.odin b/core/os/allocators.odin new file mode 100644 index 000000000..36a7d72be --- /dev/null +++ b/core/os/allocators.odin @@ -0,0 +1,74 @@ +#+private +package os2 + +import "base:runtime" + +@(require_results) +file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() +} + +@(private="file") +MAX_TEMP_ARENA_COUNT :: 2 +@(private="file") +MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1 +@(private="file", thread_local) +global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena + +@(fini, private) +temp_allocator_fini :: proc "contextless" () { + for &arena in global_default_temp_allocator_arenas { + runtime.arena_destroy(&arena) + } + global_default_temp_allocator_arenas = {} +} + +Temp_Allocator :: struct { + using arena: ^runtime.Arena, + using allocator: runtime.Allocator, + tmp: runtime.Arena_Temp, + loc: runtime.Source_Code_Location, +} + +TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) { + runtime.arena_temp_end(temp.tmp, temp.loc) +} + +@(deferred_out=TEMP_ALLOCATOR_GUARD_END) +TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator { + assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!") + good_arena: ^runtime.Arena + for i in 0.. (runtime.Arena_Temp) { + return temp_allocator_begin(tmp.arena) +} +@(private="file") +_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) { + temp_allocator_end(tmp) +} + +@(init, private) +init_thread_local_cleaner :: proc "contextless" () { + runtime.add_thread_local_cleaner(temp_allocator_fini) +} diff --git a/core/os/dir.odin b/core/os/dir.odin new file mode 100644 index 000000000..9ad5f451e --- /dev/null +++ b/core/os/dir.odin @@ -0,0 +1,262 @@ +package os2 + +import "base:runtime" +import "core:slice" +import "core:strings" + +read_dir :: read_directory + +/* + 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`. +*/ +@(require_results) +read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + if f == nil { + return nil, .Invalid_File + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + it := read_directory_iterator_create(f) + defer _read_directory_iterator_destroy(&it) + + dfi := make([dynamic]File_Info, 0, size, temp_allocator) + defer if err != nil { + for fi in dfi { + file_info_delete(fi, allocator) + } + } + + for fi, index in read_directory_iterator(&it) { + if n > 0 && index == n { + break + } + + _ = read_directory_iterator_error(&it) or_break + + append(&dfi, file_info_clone(fi, allocator) or_return) + } + + _ = read_directory_iterator_error(&it) or_return + + return slice.clone(dfi[:], allocator) +} + + +/* + Reads the file `f` (assuming it is a directory) and returns all of the unsorted directory entries. +*/ +@(require_results) +read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory(f, -1, allocator) +} + +/* + 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`. +*/ +@(require_results) +read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + f := open(path) or_return + defer close(f) + return read_directory(f, n, allocator) +} + +/* + Reads the named directory by path (assuming it is a directory) and returns all of the unsorted directory entries. +*/ +@(require_results) +read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory_by_path(path, -1, allocator) +} + + + +Read_Directory_Iterator :: struct { + f: ^File, + err: struct { + err: Error, + path: [dynamic]byte, + }, + index: int, + impl: Read_Directory_Iterator_Impl, +} + +/* +Creates a directory iterator with the given directory. + +For an example on how to use the iterator, see `read_directory_iterator`. +*/ +read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator) { + read_directory_iterator_init(&it, f) + return +} + +/* +Initialize a directory iterator with the given directory. + +This procedure may be called on an existing iterator to reuse it for another directory. + +For an example on how to use the iterator, see `read_directory_iterator`. +*/ +read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + it.err.err = nil + it.err.path.allocator = file_allocator() + clear(&it.err.path) + + it.f = f + it.index = 0 + + _read_directory_iterator_init(it, f) +} + +/* +Destroys a directory iterator. +*/ +read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil { + return + } + + delete(it.err.path) + + _read_directory_iterator_destroy(it) +} + +/* +Retrieve the last error that happened during iteration. +*/ +@(require_results) +read_directory_iterator_error :: proc(it: ^Read_Directory_Iterator) -> (path: string, err: Error) { + return string(it.err.path[:]), it.err.err +} + +@(private) +read_directory_iterator_set_error :: proc(it: ^Read_Directory_Iterator, path: string, err: Error) { + if err == nil { + return + } + + resize(&it.err.path, len(path)) + copy(it.err.path[:], path) + + it.err.err = err +} + +/* +Returns the next file info entry for the iterator's directory. + +The given `File_Info` is reused in subsequent calls so a copy (`file_info_clone`) has to be made to +extend its lifetime. + +Example: + package main + + import "core:fmt" + import "core:os" + + main :: proc() { + f, oerr := os.open("core") + ensure(oerr == nil) + defer os.close(f) + + it := os.read_directory_iterator_create(f) + defer os.read_directory_iterator_destroy(&it) + + for info in os.read_directory_iterator(&it) { + // Optionally break on the first error: + // Supports not doing this, and keeping it going with remaining items. + // _ = os.read_directory_iterator_error(&it) or_break + + // Handle error as we go: + // Again, no need to do this as it will keep going with remaining items. + if path, err := os.read_directory_iterator_error(&it); err != nil { + fmt.eprintfln("failed reading %s: %s", path, err) + continue + } + + // Or, do not handle errors during iteration, and just check the error at the end. + + + fmt.printfln("%#v", info) + } + + // Handle error if one happened during iteration at the end: + if path, err := os.read_directory_iterator_error(&it); err != nil { + fmt.eprintfln("read directory failed at %s: %s", path, err) + } + } +*/ +@(require_results) +read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + if it.f == nil { + return + } + + if it.index == 0 && it.err.err != nil { + return + } + + return _read_directory_iterator(it) +} + +// Recursively copies a directory to `dst` from `src` +copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + when #defined(_copy_directory_all_native) { + return _copy_directory_all_native(dst, src, dst_perm) + } else { + return _copy_directory_all(dst, src, dst_perm) + } +} + +@(private) +_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + err := make_directory(dst, dst_perm) + if err != nil && err != .Exist { + return err + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + abs_src := get_absolute_path(src, temp_allocator) or_return + abs_dst := get_absolute_path(dst, temp_allocator) or_return + + dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return + + w: Walker + walker_init_path(&w, src) + defer walker_destroy(&w) + + for info in walker_walk(&w) { + _ = walker_error(&w) or_break + + rel := strings.trim_prefix(info.fullpath, abs_src) + + non_zero_resize(&dst_buf, 0) + reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return + append(&dst_buf, abs_dst) + append(&dst_buf, Path_Separator_String) + append(&dst_buf, rel) + + if info.type == .Directory { + err = make_directory(string(dst_buf[:]), dst_perm) + if err != nil && err != .Exist { + return err + } + } else { + copy_file(string(dst_buf[:]), info.fullpath) or_return + } + } + + _ = walker_error(&w) or_return + + return nil +} diff --git a/core/os/dir_js.odin b/core/os/dir_js.odin new file mode 100644 index 000000000..d8f7c6202 --- /dev/null +++ b/core/os/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/dir_linux.odin b/core/os/dir_linux.odin new file mode 100644 index 000000000..34346c02f --- /dev/null +++ b/core/os/dir_linux.odin @@ -0,0 +1,120 @@ +#+private +package os2 + +import "core:sys/linux" + +Read_Directory_Iterator_Impl :: struct { + prev_fi: File_Info, + dirent_backing: []u8, + dirent_buflen: int, + dirent_off: int, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + scan_entries :: proc(it: ^Read_Directory_Iterator, dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) { + for d in linux.dirent_iterate_buf(entries, offset) { + file_name = linux.dirent_name(d) + if file_name == "." || file_name == ".." { + continue + } + + file_name_cstr := cstring(raw_data(file_name)) + entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH}) + if errno == .NONE { + return entry_fd, file_name + } else { + read_directory_iterator_set_error(it, file_name, _get_platform_error(errno)) + } + } + + return -1, "" + } + + index = it.index + it.index += 1 + + dfd := linux.Fd(_fd(it.f)) + + entries := it.impl.dirent_backing[:it.impl.dirent_buflen] + entry_fd, file_name := scan_entries(it, dfd, entries, &it.impl.dirent_off) + + for entry_fd == -1 { + if len(it.impl.dirent_backing) == 0 { + it.impl.dirent_backing = make([]u8, 512, file_allocator()) + } + + loop: for { + buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:]) + #partial switch errno { + case .EINVAL: + delete(it.impl.dirent_backing, file_allocator()) + n := len(it.impl.dirent_backing) * 2 + it.impl.dirent_backing = make([]u8, n, file_allocator()) + continue + case .NONE: + if buflen == 0 { + return + } + it.impl.dirent_off = 0 + it.impl.dirent_buflen = buflen + entries = it.impl.dirent_backing[:buflen] + break loop + case: + read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno)) + return + } + } + + entry_fd, file_name = scan_entries(it, dfd, entries, &it.impl.dirent_off) + } + defer linux.close(entry_fd) + + // PERF: reuse the fullpath string like on posix and wasi. + file_info_delete(it.impl.prev_fi, file_allocator()) + + err: Error + fi, err = _fstat_internal(entry_fd, file_allocator()) + it.impl.prev_fi = fi + + if err != nil { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path, _ := _get_full_path(entry_fd, temp_allocator) + read_directory_iterator_set_error(it, path, err) + } + + ok = true + return +} + +_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + // NOTE: Allow calling `init` to target a new directory with the same iterator. + it.impl.dirent_buflen = 0 + it.impl.dirent_off = 0 + + if f == nil || f.impl == nil { + read_directory_iterator_set_error(it, "", .Invalid_File) + return + } + + stat: linux.Stat + errno := linux.fstat(linux.Fd(fd(f)), &stat) + if errno != .NONE { + read_directory_iterator_set_error(it, name(f), _get_platform_error(errno)) + return + } + + if (stat.mode & linux.S_IFMT) != linux.S_IFDIR { + read_directory_iterator_set_error(it, name(f), .Invalid_Dir) + return + } +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil { + return + } + + delete(it.impl.dirent_backing, file_allocator()) + file_info_delete(it.impl.prev_fi, file_allocator()) +} diff --git a/core/os/dir_posix.odin b/core/os/dir_posix.odin new file mode 100644 index 000000000..d9fa16f8d --- /dev/null +++ b/core/os/dir_posix.odin @@ -0,0 +1,104 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +Read_Directory_Iterator_Impl :: struct { + dir: posix.DIR, + fullpath: [dynamic]byte, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + index = it.index + it.index += 1 + + for { + posix.set_errno(nil) + entry := posix.readdir(it.impl.dir) + if entry == nil { + if errno := posix.errno(); errno != nil { + read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno)) + } + return + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + sname := string(cname) + + n := len(fimpl.name)+1 + if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil { + read_directory_iterator_set_error(it, sname, err) + ok = true + return + } + copy(it.impl.fullpath[n:], sname) + + stat: posix.stat_t + if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { + read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error()) + ok = true + return + } + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + if f == nil || f.impl == nil { + read_directory_iterator_set_error(it, "", .Invalid_File) + return + } + + impl := (^File_Impl)(f.impl) + + // NOTE: Allow calling `init` to target a new directory with the same iterator. + it.impl.fullpath.allocator = file_allocator() + clear(&it.impl.fullpath) + if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil { + read_directory_iterator_set_error(it, name(f), err) + return + } + + append(&it.impl.fullpath, impl.name) + append(&it.impl.fullpath, "/") + + // `fdopendir` consumes the file descriptor so we need to `dup` it. + dupfd := posix.dup(impl.fd) + if dupfd == -1 { + read_directory_iterator_set_error(it, name(f), _get_platform_error()) + return + } + defer if it.err.err != nil { posix.close(dupfd) } + + // NOTE: Allow calling `init` to target a new directory with the same iterator. + if it.impl.dir != nil { + posix.closedir(it.impl.dir) + } + + it.impl.dir = posix.fdopendir(dupfd) + if it.impl.dir == nil { + read_directory_iterator_set_error(it, name(f), _get_platform_error()) + return + } + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it.impl.dir == nil { + return + } + + posix.closedir(it.impl.dir) + delete(it.impl.fullpath) +} diff --git a/core/os/dir_posix_darwin.odin b/core/os/dir_posix_darwin.odin new file mode 100644 index 000000000..3cae50d25 --- /dev/null +++ b/core/os/dir_posix_darwin.odin @@ -0,0 +1,17 @@ +#+private +package os2 + +import "core:sys/darwin" + +_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src, temp_allocator) or_return + cdst := clone_to_cstring(dst, temp_allocator) or_return + + if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 { + err = _get_platform_error() + } + + return +} diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin deleted file mode 100644 index c3dd844ef..000000000 --- a/core/os/dir_unix.odin +++ /dev/null @@ -1,65 +0,0 @@ -#+build darwin, linux, netbsd, freebsd, openbsd, haiku -package os - -import "core:strings" - -@(require_results) -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { - context.allocator = allocator - - dupfd := _dup(fd) or_return - dirp := _fdopendir(dupfd) or_return - defer _closedir(dirp) - - dirpath := absolute_path_from_handle(dupfd) or_return - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) or_return - defer if err != nil { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - } - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != nil { - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := string(cstring(&entry.name[0])) - - if filename == "." || filename == ".." { - continue - } - - fullpath := strings.join({ dirpath, filename }, "/", allocator) - - s: OS_Stat - s, err = _lstat(fullpath) - if err != nil { - delete(fullpath, allocator) - return - } - _fill_file_info_from_stat(&fi_, s) - fi_.fullpath = fullpath - fi_.name = path_base(fi_.fullpath) - - append(&dfi, fi_) - } - - return dfi[:], nil -} diff --git a/core/os/dir_walker.odin b/core/os/dir_walker.odin new file mode 100644 index 000000000..4dce884a8 --- /dev/null +++ b/core/os/dir_walker.odin @@ -0,0 +1,230 @@ +package os2 + +import "core:container/queue" + +/* +A recursive directory walker. + +Note that none of the fields should be accessed directly. +*/ +Walker :: struct { + todo: queue.Queue(string), + skip_dir: bool, + err: struct { + path: [dynamic]byte, + err: Error, + }, + iter: Read_Directory_Iterator, +} + +walker_init_path :: proc(w: ^Walker, path: string) { + cloned_path, err := clone_string(path, file_allocator()) + if err != nil { + walker_set_error(w, path, err) + return + } + + walker_clear(w) + + if _, err = queue.push(&w.todo, cloned_path); err != nil { + walker_set_error(w, cloned_path, err) + return + } +} + +walker_init_file :: proc(w: ^Walker, f: ^File) { + handle, err := clone(f) + if err != nil { + path, _ := clone_string(name(f), file_allocator()) + walker_set_error(w, path, err) + return + } + + walker_clear(w) + + read_directory_iterator_init(&w.iter, handle) +} + +/* +Initializes a walker, either using a path or a file pointer to a directory the walker will start at. + +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 :: proc { + walker_init_path, + walker_init_file, +} + +@(require_results) +walker_create_path :: proc(path: string) -> (w: Walker) { + walker_init_path(&w, path) + return +} + +@(require_results) +walker_create_file :: proc(f: ^File) -> (w: Walker) { + walker_init_file(&w, f) + return +} + +/* +Creates a walker, either using a path or a file pointer to a directory the walker will start at. + +For an example on how to use the walker, see `walker_walk`. +*/ +walker_create :: proc { + walker_create_path, + walker_create_file, +} + +/* +Returns the last error that occurred during the walker's operations. + +Can be called while iterating, or only at the end to check if anything failed. +*/ +@(require_results) +walker_error :: proc(w: ^Walker) -> (path: string, err: Error) { + return string(w.err.path[:]), w.err.err +} + +@(private) +walker_set_error :: proc(w: ^Walker, path: string, err: Error) { + if err == nil { + return + } + + resize(&w.err.path, len(path)) + copy(w.err.path[:], path) + + w.err.err = err +} + +@(private) +walker_clear :: proc(w: ^Walker) { + w.iter.f = nil + w.skip_dir = false + + w.err.path.allocator = file_allocator() + clear(&w.err.path) + + w.todo.data.allocator = file_allocator() + for path in queue.pop_front_safe(&w.todo) { + delete(path, file_allocator()) + } +} + +walker_destroy :: proc(w: ^Walker) { + walker_clear(w) + queue.destroy(&w.todo) + delete(w.err.path) + read_directory_iterator_destroy(&w.iter) +} + +// Marks the current directory to be skipped (not entered into). +walker_skip_dir :: proc(w: ^Walker) { + w.skip_dir = true +} + +/* +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 "core:os" + + 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) + } + } +*/ +@(require_results) +walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) { + if w.skip_dir { + w.skip_dir = false + if skip, sok := queue.pop_back_safe(&w.todo); sok { + delete(skip, file_allocator()) + } + } + + if w.iter.f == nil { + if queue.len(w.todo) == 0 { + return + } + + next := queue.pop_front(&w.todo) + + handle, err := open(next) + if err != nil { + walker_set_error(w, next, err) + return {}, true + } + + read_directory_iterator_init(&w.iter, handle) + + delete(next, file_allocator()) + } + + info, _, iter_ok := read_directory_iterator(&w.iter) + + if path, err := read_directory_iterator_error(&w.iter); err != nil { + walker_set_error(w, path, err) + } + + if !iter_ok { + close(w.iter.f) + w.iter.f = nil + return walker_walk(w) + } + + if info.type == .Directory { + path, err := clone_string(info.fullpath, file_allocator()) + if err != nil { + walker_set_error(w, "", err) + return + } + + _, err = queue.push_back(&w.todo, path) + if err != nil { + walker_set_error(w, path, err) + return + } + } + + return info, iter_ok +} \ No newline at end of file diff --git a/core/os/dir_wasi.odin b/core/os/dir_wasi.odin new file mode 100644 index 000000000..9804f07fd --- /dev/null +++ b/core/os/dir_wasi.odin @@ -0,0 +1,123 @@ +#+private +package os2 + +import "core:slice" +import "base:intrinsics" +import "core:sys/wasm/wasi" + +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) { + fimpl := (^File_Impl)(it.f.impl) + + buf := it.impl.buf[it.impl.off:] + + index = it.index + it.index += 1 + + for { + if len(buf) < size_of(wasi.dirent_t) { + return + } + + entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf))) + buf = buf[size_of(wasi.dirent_t):] + + assert(len(buf) < int(entry.d_namlen)) + + name := string(buf[:entry.d_namlen]) + buf = buf[entry.d_namlen:] + it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen) + + if name == "." || name == ".." { + continue + } + + n := len(fimpl.name)+1 + if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil { + read_directory_iterator_set_error(it, name, alloc_err) + ok = true + return + } + copy(it.impl.fullpath[n:], name) + + stat, err := wasi.path_filestat_get(__fd(it.f), {}, name) + if err != nil { + // Can't stat, fill what we have from dirent. + stat = { + ino = entry.d_ino, + filetype = entry.d_type, + } + read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error(err)) + } + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { + // NOTE: Allow calling `init` to target a new directory with the same iterator. + it.impl.off = 0 + + if f == nil || f.impl == nil { + read_directory_iterator_set_error(it, "", .Invalid_File) + return + } + + impl := (^File_Impl)(f.impl) + + buf: [dynamic]byte + // NOTE: Allow calling `init` to target a new directory with the same iterator. + if it.impl.buf != nil { + buf = slice.into_dynamic(it.impl.buf) + } + buf.allocator = file_allocator() + + defer if it.err.err != nil { delete(buf) } + + for { + if err := non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2); err != nil { + read_directory_iterator_set_error(it, name(f), err) + return + } + + n, err := wasi.fd_readdir(__fd(f), buf[:], 0) + if err != nil { + read_directory_iterator_set_error(it, name(f), _get_platform_error(err)) + return + } + + if n < len(buf) { + non_zero_resize(&buf, n) + break + } + + assert(n == len(buf)) + } + it.impl.buf = buf[:] + + // NOTE: Allow calling `init` to target a new directory with the same iterator. + it.impl.fullpath.allocator = file_allocator() + clear(&it.impl.fullpath) + if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil { + read_directory_iterator_set_error(it, name(f), err) + return + } + + append(&it.impl.fullpath, impl.name) + append(&it.impl.fullpath, "/") + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + delete(it.impl.buf, file_allocator()) + delete(it.impl.fullpath) +} 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 - } - 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 fi.name != "" { + it.impl.prev_fi = fi + ok = true + index = it.index + it.index += 1 } - 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 - } - - context.allocator = allocator - - h := win32.HANDLE(fd) + it.f = f + impl := (^File_Impl)(f.impl) - dir_fi, _ := file_info_from_get_file_information_by_handle("", h) - if !dir_fi.is_dir { - return nil, .Not_Dir + // 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) } - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 + if it.impl.path != "" { + delete(it.impl.path, file_allocator()) } - 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 if it.err.err != nil { + win32.FindClose(it.impl.find_handle) } - 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 - } + 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 dfi[:], nil + 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/doc.odin b/core/os/doc.odin new file mode 100644 index 000000000..2ebdd0912 --- /dev/null +++ b/core/os/doc.odin @@ -0,0 +1,6 @@ +// Package os 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. +// Features not generally available appear in the system-specific packages under core:sys/*. +package os2 diff --git a/core/os/env.odin b/core/os/env.odin new file mode 100644 index 000000000..310d45af1 --- /dev/null +++ b/core/os/env.odin @@ -0,0 +1,122 @@ +package os2 + +import "base:runtime" +import "core:strings" + +// `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: runtime.Allocator) -> string { + value, _ := lookup_env(key, allocator) + return value +} + +// `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: this version takes a backing buffer for the string value +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> string { + value, _ := lookup_env(buf, key) + return value +} + +get_env :: proc{get_env_alloc, get_env_buf} + +// `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: runtime.Allocator) -> (value: string, found: bool) { + return _lookup_env_alloc(key, allocator) +} + +// 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) { + return _lookup_env_buf(buf, key) +} + +lookup_env :: proc{lookup_env_alloc, lookup_env_buf} + +// set_env sets the value of the environment variable named by the key +// Returns Error on failure +set_env :: proc(key, value: string) -> Error { + return _set_env(key, value) +} + +// unset_env unsets a single environment variable +// Returns true on success, false on failure +unset_env :: proc(key: string) -> bool { + return _unset_env(key) +} + +clear_env :: proc() { + _clear_env() +} + + +// 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: runtime.Allocator) -> ([]string, Error) { + return _environ(allocator) +} + +// Always allocates for consistency. +replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) { + path := path + + sb: strings.Builder + strings.builder_init_none(&sb, allocator) + + for len(path) > 0 { + switch path[0] { + case '%': // Windows + when ODIN_OS == .Windows { + for r, i in path[1:] { + if r == '%' { + env_key := path[1:i+1] + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[i+1:] // % is part of key, so skip 1 character extra + } + } + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case '$': // Posix + when ODIN_OS != .Windows { + env_key := "" + dollar_loop: for r, i in path[1:] { + switch r { + case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident + case: + env_key = path[1:i+1] + break dollar_loop + } + } + if len(env_key) > 0 { + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[len(env_key):] + } + + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case: + strings.write_rune(&sb, rune(path[0])) + } + + path = path[1:] + } + return strings.to_string(sb) +} \ No newline at end of file diff --git a/core/os/env_js.odin b/core/os/env_js.odin new file mode 100644 index 000000000..c1d94ba4a --- /dev/null +++ b/core/os/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/env_linux.odin b/core/os/env_linux.odin new file mode 100644 index 000000000..7855fbfed --- /dev/null +++ b/core/os/env_linux.odin @@ -0,0 +1,369 @@ +#+private +package os2 + +import "base:runtime" +import "base:intrinsics" + +import "core:sync" +import "core:slice" +import "core:strings" +import "core:sys/linux" +import "core:sys/posix" + +_ :: sync +_ :: slice +_ :: linux +_ :: posix + +when ODIN_NO_CRT { + // TODO: Override the libc environment functions' weak linkage to + // allow us to interact with 3rd party code that DOES link + // to libc. Otherwise, our environment can be out of sync. + + NOT_FOUND :: -1 + + // the environment is a 0 delimited list of = strings + _env: [dynamic]string + + _env_mutex: sync.Recursive_Mutex + + // We need to be able to figure out if the environment variable + // is contained in the original environment or not. This also + // serves as a flag to determine if we have built _env. + _org_env_begin: uintptr // atomic + _org_env_end: uintptr // guarded by _env_mutex + + // Returns value + index location into _env + // or -1 if not found + _lookup :: proc(key: string) -> (value: string, idx: int) { + sync.guard(&_env_mutex) + + for entry, i in _env { + if k, v := _kv_from_entry(entry); k == key { + return v, i + } + } + return "", -1 + } + + _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + found = true + value, _ = clone_string(v, allocator) + } + return + } + + _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + if len(buf) >= len(v) { + copy(buf, v) + return string(buf[:len(v)]), nil + } + return "", .Buffer_Full + } + return "", .Env_Var_Not_Found + } + + _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + + _set_env :: proc(key, v_new: string) -> Error { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + // all key values are stored as "key=value\x00" + kv_size := len(key) + len(v_new) + 2 + if v_curr, idx := _lookup(key); idx != NOT_FOUND { + if v_curr == v_new { + return nil + } + + unordered_remove(&_env, idx) + + if !_is_in_org_env(v_curr) { + // We allocated this key-value. Possibly resize and + // overwrite the value only. Otherwise, treat as if it + // wasn't in the environment in the first place. + k_addr, v_addr := _kv_addr_from_val(v_curr, key) + if len(v_new) > len(v_curr) { + k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) + if k_addr == nil { + return .Out_Of_Memory + } + v_addr = &k_addr[len(key) + 1] + } + intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) + v_addr[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size])) + return nil + } + } + + k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) + if k_addr == nil { + return .Out_Of_Memory + } + intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) + k_addr[len(key)] = '=' + + val_slice := k_addr[len(key) + 1:] + intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) + val_slice[len(v_new)] = 0 + + append(&_env, string(k_addr[:kv_size - 1])) + return nil + } + + _unset_env :: proc(key: string) -> bool { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + v: string + i: int + if v, i = _lookup(key); i == -1 { + return true + } + + unordered_remove(&_env, i) + + if _is_in_org_env(v) { + return true + } + + // if we got this far, the environment variable + // existed AND was allocated by us. + k_addr, _ := _kv_addr_from_val(v, key) + runtime.heap_free(k_addr) + return true + } + + _clear_env :: proc() { + sync.guard(&_env_mutex) + + for kv in _env { + if !_is_in_org_env(kv) { + runtime.heap_free(raw_data(kv)) + } + } + clear(&_env) + + // nothing resides in the original environment either + intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release) + _org_env_end = ~uintptr(0) + } + + _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + sync.guard(&_env_mutex) + + env := make([dynamic]string, 0, len(_env), allocator) or_return + defer if err != nil { + for e in env { + delete(e, allocator) + } + delete(env) + } + + for entry in _env { + s := clone_string(entry, allocator) or_return + append(&env, s) + } + environ = env[:] + return + } + + // The entire environment is stored as 0 terminated strings, + // so there is no need to clone/free individual variables + export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + // The environment has not been modified, so we can just + // send the original environment + org_env := _get_original_env() + n: int + for ; org_env[n] != nil; n += 1 {} + return slice.clone(org_env[:n + 1], allocator) + } + sync.guard(&_env_mutex) + + // NOTE: already terminated by nil pointer via + 1 + env := make([]cstring, len(_env) + 1, allocator) + + for entry, i in _env { + env[i] = cstring(raw_data(entry)) + } + return env + } + + _build_env :: proc() { + sync.guard(&_env_mutex) + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 { + return + } + + _env = make(type_of(_env), runtime.heap_allocator()) + cstring_env := _get_original_env() + intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release) + for i := 0; cstring_env[i] != nil; i += 1 { + bytes := ([^]u8)(cstring_env[i]) + n := len(cstring_env[i]) + _org_env_end = uintptr(&bytes[n]) + append(&_env, string(bytes[:n])) + } + } + + _get_original_env :: #force_inline proc() -> [^]cstring { + // essentially &argv[argc] which should be a nil pointer! + #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] + assert(env[0] == nil) + return &env[1] + } + + _kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { + eq_idx := strings.index_byte(entry, '=') + if eq_idx == -1 { + return entry, "" + } + return entry[:eq_idx], entry[eq_idx + 1:] + } + + _kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { + v_addr := raw_data(val) + k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) + return k_addr, v_addr + } + + _is_in_org_env :: #force_inline proc(env_data: string) -> bool { + addr := uintptr(raw_data(env_data)) + return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end + } + +} else { + + _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + ckey := strings.clone_to_cstring(key, temp_allocator) + cval := posix.getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return + } + + _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + cval := posix.getenv(cstring(raw_data(buf))) + if cval == nil { + return + } + + if value = string(cval); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } + } + + _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + + _set_env :: proc(key, value: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) or_return + cval := strings.clone_to_cstring(value, temp_allocator) or_return + + if posix.setenv(ckey, cval, true) != nil { + posix_errno := posix.errno() + linux_errno := cast(linux.Errno)(cast(int)posix_errno) + err = _get_platform_error(linux_errno) + } + return + } + + _unset_env :: proc(key: string) -> (ok: bool) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) + + ok = posix.unsetenv(ckey) == .OK + return + } + + // NOTE(laytan): clearing the env is weird, why would you ever do that? + + _clear_env :: proc() { + for entry := posix.environ[0]; entry != nil; entry = posix.environ[0] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } + } + + _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + n := 0 + for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} + + r := make([dynamic]string, 0, n, allocator) or_return + defer if err != nil { + for e in r { + delete(e, allocator) + } + delete(r) + } + + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + append(&r, strings.clone(string(entry), allocator) or_return) + } + + environ = r[:] + return + } + + + + export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { + env := make([dynamic]cstring, allocator) + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + append(&env, entry) + } + append(&env, nil) + return env[:] + } +} diff --git a/core/os/env_posix.odin b/core/os/env_posix.odin new file mode 100644 index 000000000..72a1daf18 --- /dev/null +++ b/core/os/env_posix.odin @@ -0,0 +1,110 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + ckey := strings.clone_to_cstring(key, temp_allocator) + cval := posix.getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return +} + +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + cval := posix.getenv(cstring(raw_data(buf))) + if cval == nil { + return + } + + if value = string(cval); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} + +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + +_set_env :: proc(key, value: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) or_return + cval := strings.clone_to_cstring(value, temp_allocator) or_return + + if posix.setenv(ckey, cval, true) != nil { + err = _get_platform_error_from_errno() + } + return +} + +_unset_env :: proc(key: string) -> (ok: bool) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + ckey := strings.clone_to_cstring(key, temp_allocator) + + ok = posix.unsetenv(ckey) == .OK + return +} + +// NOTE(laytan): clearing the env is weird, why would you ever do that? + +_clear_env :: proc() { + for entry := posix.environ[0]; entry != nil; entry = posix.environ[0] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } +} + +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + n := 0 + for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} + + r := make([dynamic]string, 0, n, allocator) or_return + defer if err != nil { + for e in r { + delete(e, allocator) + } + delete(r) + } + + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + append(&r, strings.clone(string(entry), allocator) or_return) + } + + environ = r[:] + return +} + + diff --git a/core/os/env_wasi.odin b/core/os/env_wasi.odin new file mode 100644 index 000000000..cb40667cf --- /dev/null +++ b/core/os/env_wasi.odin @@ -0,0 +1,188 @@ +#+private +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sync" +import "core:sys/wasm/wasi" + +g_env: map[string]string +g_env_buf: []byte +g_env_mutex: sync.RW_Mutex +g_env_error: Error +g_env_built: bool + +build_env :: proc() -> (err: Error) { + if g_env_built || g_env_error != nil { + return g_env_error + } + + sync.guard(&g_env_mutex) + + if g_env_built || g_env_error != nil { + return g_env_error + } + + defer if err != nil { + g_env_error = err + } + + num_envs, size_of_envs, _err := wasi.environ_sizes_get() + if _err != nil { + return _get_platform_error(_err) + } + + g_env = make(map[string]string, num_envs, file_allocator()) or_return + defer if err != nil { delete(g_env) } + + g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return + defer if err != nil { delete(g_env_buf, file_allocator()) } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + envs := make([]cstring, num_envs, temp_allocator) or_return + + _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) + if _err != nil { + return _get_platform_error(_err) + } + + for env in envs { + key, _, value := strings.partition(string(env), "=") + g_env[key] = value + } + + g_env_built = true + return +} + +delete_string_if_not_original :: proc(str: string) { + start := uintptr(raw_data(g_env_buf)) + end := start + uintptr(len(g_env_buf)) + ptr := uintptr(raw_data(str)) + if ptr < start || ptr > end { + delete(str, file_allocator()) + } +} + +@(require_results) +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if err := build_env(); err != nil { + return + } + + sync.shared_guard(&g_env_mutex) + + value = g_env[key] or_return + value, _ = clone_string(value, allocator) + return +} + +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + sync.shared_guard(&g_env_mutex) + + val, ok := g_env[key] + + if !ok { + return "", .Env_Var_Not_Found + } else { + if len(val) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, val) + return string(buf[:len(val)]), nil + } + } +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + +@(require_results) +_set_env :: proc(key, value: string) -> (err: Error) { + build_env() or_return + + sync.guard(&g_env_mutex) + + defer if err != nil { + delete_key(&g_env, key) + } + + key_ptr, value_ptr, just_inserted := map_entry(&g_env, key) or_return + + if just_inserted { + key_ptr^ = clone_string(key, file_allocator()) or_return + defer if err != nil { + delete(key_ptr^, file_allocator()) + } + value_ptr^ = clone_string(value, file_allocator()) or_return + return + } + + delete_string_if_not_original(value_ptr^) + + value_ptr^ = clone_string(value, file_allocator()) or_return + return +} + +@(require_results) +_unset_env :: proc(key: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + dkey, dval := delete_key(&g_env, key) + delete_string_if_not_original(dkey) + delete_string_if_not_original(dval) + return true +} + +_clear_env :: proc() { + sync.guard(&g_env_mutex) + + for k, v in g_env { + delete_string_if_not_original(k) + delete_string_if_not_original(v) + } + + delete(g_env_buf, file_allocator()) + g_env_buf = {} + + clear(&g_env) + + g_env_built = true +} + +@(require_results) +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { + build_env() or_return + + sync.shared_guard(&g_env_mutex) + + envs := make([dynamic]string, 0, len(g_env), allocator) or_return + defer if err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs) + } + + for k, v in g_env { + append(&envs, concatenate({k, "=", v}, allocator) or_return) + } + + environ = envs[:] + return +} 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) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + wkey, _ := win32_utf8_to_wstring(key, temp_allocator) + n := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { - return "", false + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + return "", true } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - b, _ := make([dynamic]u16, n, context.temp_allocator) + b := make([]u16, n+1, 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,84 +64,79 @@ lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) 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 -} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} -@(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() +_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.. (err: i32, ok: bool) { +is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { v := ferr.(Platform_Error) or_else {} return i32(v), i32(v) != 0 } + +// Attempts to return the error `ferr` as a string without any allocation @(require_results) -error_string :: proc "contextless" (ferr: Error) -> string { +error_string :: proc(ferr: Error) -> string { if ferr == nil { return "" } @@ -62,18 +65,19 @@ error_string :: proc "contextless" (ferr: Error) -> string { case General_Error: switch e { case .None: return "" - case .Exist: return "file already exists" - case .Not_Exist: return "file does not exist" - case .Timeout: return "i/o timeout" - case .Broken_Pipe: return "Broken pipe" - case .Invalid_File: return "invalid file" - case .Invalid_Dir: return "invalid directory" - case .Invalid_Path: return "invalid path" - case .Invalid_Callback: return "invalid callback" - case .Pattern_Has_Separator: return "pattern has separator" - case .File_Is_Pipe: return "file is pipe" - case .Not_Dir: return "file is not directory" - case .Env_Var_Not_Found: return "environment variable not found" + case .Exist: return "file already exists" + case .Not_Exist: return "file does not exist" + case .Timeout: return "i/o timeout" + case .Broken_Pipe: return "Broken pipe" + case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" + case .Invalid_Path: return "invalid path" + 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" } case io.Error: switch e { @@ -106,210 +110,35 @@ error_string :: proc "contextless" (ferr: Error) -> string { case .Mode_Not_Implemented: return "allocator mode not implemented" } case Platform_Error: - return _error_string(e) + return _error_string(i32(e)) } return "unknown error" } -print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) { +/* + `print_error` is a utility procedure which will print an error `ferr` to a specified file `f`. +*/ +print_error :: proc(f: ^File, ferr: Error, msg: string) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) err_str := error_string(ferr) // msg + ": " + err_str + '\n' length := len(msg) + 2 + len(err_str) + 1 - buf_ := intrinsics.alloca(length, 1) - buf := buf_[:length] + buf := make([]u8, length, temp_allocator) copy(buf, msg) buf[len(msg)] = ':' buf[len(msg) + 1] = ' ' copy(buf[len(msg) + 2:], err_str) buf[length - 1] = '\n' - return write(f, buf) + write(f, buf) } -@(require_results, private) -_error_string :: proc "contextless" (e: Platform_Error) -> 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/errors_js.odin b/core/os/errors_js.odin new file mode 100644 index 000000000..c92d36736 --- /dev/null +++ b/core/os/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/errors_linux.odin b/core/os/errors_linux.odin new file mode 100644 index 000000000..a7556c306 --- /dev/null +++ b/core/os/errors_linux.odin @@ -0,0 +1,177 @@ +#+private +package os2 + +import "core:sys/linux" + +_Platform_Error :: linux.Errno + +@(rodata) +_errno_strings := [linux.Errno]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", +} + + +_get_platform_error :: proc(errno: linux.Errno) -> Error { + #partial switch errno { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case .ENOSYS: + return .Unsupported + } + + return Platform_Error(i32(errno)) +} + +_error_string :: proc(errno: i32) -> string { + if errno >= 0 && errno <= i32(max(linux.Errno)) { + return _errno_strings[linux.Errno(errno)] + } + return "Unknown Error" +} diff --git a/core/os/errors_posix.odin b/core/os/errors_posix.odin new file mode 100644 index 000000000..8a9ca07df --- /dev/null +++ b/core/os/errors_posix.odin @@ -0,0 +1,43 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +_Platform_Error :: posix.Errno + +_error_string :: proc(errno: i32) -> string { + return string(posix.strerror(posix.Errno(errno))) +} + +_get_platform_error_from_errno :: proc() -> Error { + return _get_platform_error_existing(posix.errno()) +} + +_get_platform_error_existing :: proc(errno: posix.Errno) -> Error { + #partial switch errno { + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case .ENOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} + +_get_platform_error :: proc{ + _get_platform_error_existing, + _get_platform_error_from_errno, +} diff --git a/core/os/errors_wasi.odin b/core/os/errors_wasi.odin new file mode 100644 index 000000000..b88e5b81e --- /dev/null +++ b/core/os/errors_wasi.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:slice" +import "core:sys/wasm/wasi" + +_Platform_Error :: wasi.errno_t + +_error_string :: proc(errno: i32) -> string { + e := wasi.errno_t(errno) + if e == .NONE { + return "" + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" +} + +_get_platform_error :: proc(errno: wasi.errno_t) -> Error { + #partial switch errno { + case .PERM: + return .Permission_Denied + case .EXIST: + return .Exist + case .NOENT: + return .Not_Exist + case .TIMEDOUT: + return .Timeout + case .PIPE: + return .Broken_Pipe + case .BADF: + return .Invalid_File + case .NOMEM: + return .Out_Of_Memory + case .NOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} diff --git a/core/os/errors_windows.odin b/core/os/errors_windows.odin new file mode 100644 index 000000000..404560f98 --- /dev/null +++ b/core/os/errors_windows.odin @@ -0,0 +1,78 @@ +#+private +package os2 + +import "base:runtime" +import "core:slice" +import win32 "core:sys/windows" + +_Platform_Error :: win32.System_Error + +_error_string :: proc(errno: i32) -> string { + e := win32.DWORD(errno) + if e == 0 { + return "" + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" +} + +_get_platform_error :: proc() -> Error { + err := win32.GetLastError() + if err == 0 { + return nil + } + switch err { + case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: + return .Permission_Denied + + case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: + return .Exist + + case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: + return .Not_Exist + + case win32.ERROR_NO_DATA: + return .Closed + + case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: + return .Timeout + + case win32.ERROR_NOT_SUPPORTED: + return .Unsupported + + case win32.ERROR_HANDLE_EOF: + return .EOF + + case win32.ERROR_INVALID_HANDLE: + return .Invalid_File + + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + + case win32.ERROR_BROKEN_PIPE: + return .Broken_Pipe + + case + win32.ERROR_BAD_ARGUMENTS, + win32.ERROR_INVALID_PARAMETER, + win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_NO_MORE_FILES, + win32.ERROR_LOCK_VIOLATION, + win32.ERROR_CALL_NOT_IMPLEMENTED, + win32.ERROR_INSUFFICIENT_BUFFER, + win32.ERROR_INVALID_NAME, + win32.ERROR_LOCK_FAILED, + win32.ERROR_ENVVAR_NOT_FOUND, + win32.ERROR_OPERATION_ABORTED, + win32.ERROR_IO_PENDING, + win32.ERROR_NO_UNICODE_TRANSLATION: + // fallthrough + } + return Platform_Error(err) +} diff --git a/core/os/file.odin b/core/os/file.odin new file mode 100644 index 000000000..bf7ebaeb5 --- /dev/null +++ b/core/os/file.odin @@ -0,0 +1,570 @@ +package os2 + +import "core:io" +import "core:time" +import "base:runtime" + +/* + Type representing a file handle. + + This struct represents an OS-specific file-handle, which can be one of + the following: + - File + - Directory + - Pipe + - Named pipe + - Block Device + - Character device + - Symlink + - Socket + + See `File_Type` enum for more information on file types. +*/ +File :: struct { + impl: rawptr, + stream: File_Stream, +} + +/* + Type representing the type of a file handle. + + **Note(windows)**: Socket handles can not be distinguished from + files, as they are just a normal file handle that is being treated by + a special driver. Windows also makes no distinction between block and + character devices. +*/ +File_Type :: enum { + // The type of a file could not be determined for the current platform. + Undetermined, + // Represents a regular file. + Regular, + // Represents a directory. + Directory, + // Represents a symbolic link. + Symlink, + // Represents a named pipe (FIFO). + Named_Pipe, + // Represents a socket. + // **Note(windows)**: Not returned on windows + Socket, + // Represents a block device. + // **Note(windows)**: On windows represents all devices. + Block_Device, + // Represents a character device. + // **Note(windows)**: Not returned on windows + Character_Device, +} + +// Represents the file flags for a file handle +File_Flags :: distinct bit_set[File_Flag; uint] +File_Flag :: enum { + Read, + Write, + Append, + Create, + Excl, + Sync, + Trunc, + Sparse, + Inheritable, + Non_Blocking, + Unbuffered_IO, +} + +O_RDONLY :: File_Flags{.Read} +O_WRONLY :: File_Flags{.Write} +O_RDWR :: File_Flags{.Read, .Write} +O_APPEND :: File_Flags{.Append} +O_CREATE :: File_Flags{.Create} +O_EXCL :: File_Flags{.Excl} +O_SYNC :: File_Flags{.Sync} +O_TRUNC :: File_Flags{.Trunc} +O_SPARSE :: File_Flags{.Sparse} + +/* + If specified, the file handle is inherited upon the creation of a child + process. By default all handles are created non-inheritable. + + **Note**: The standard file handles (stderr, stdout and stdin) are always + initialized as inheritable. +*/ +O_INHERITABLE :: File_Flags{.Inheritable} + +Permissions :: distinct bit_set[Permission_Flag; u32] +Permission_Flag :: enum u32 { + Execute_Other = 0, + Write_Other = 1, + Read_Other = 2, + + Execute_Group = 3, + Write_Group = 4, + Read_Group = 5, + + Execute_User = 6, + Write_User = 7, + Read_User = 8, +} + +Permissions_Execute_All :: Permissions{.Execute_User, .Execute_Group, .Execute_Other} +Permissions_Write_All :: Permissions{.Write_User, .Write_Group, .Write_Other} +Permissions_Read_All :: Permissions{.Read_User, .Read_Group, .Read_Other} + +Permissions_Read_Write_All :: Permissions_Read_All + Permissions_Write_All + +Permissions_All :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All + +Permissions_Default_File :: Permissions_Read_All + Permissions_Write_All +Permissions_Default_Directory :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All +Permissions_Default :: Permissions_Default_Directory + +perm :: proc{ + perm_number, +} + +/* + `perm_number` converts an integer value `perm` to the bit set `Permissions` +*/ +@(require_results) +perm_number :: proc "contextless" (perm: int) -> Permissions { + return transmute(Permissions)u32(perm & 0o777) +} + + + +// `stdin` is an open file pointing to the standard input file stream +stdin: ^File = nil // OS-Specific + +// `stdout` is an open file pointing to the standard output file stream +stdout: ^File = nil // OS-Specific + +// `stderr` is an open file pointing to the standard error file stream +stderr: ^File = nil // OS-Specific + +/* + `create` creates or truncates a named file `name`. + If the file already exists, it is truncated. + If the file does not exist, it is created with the `Permissions_Default_File` permissions. + If successful, a `^File` is return which can be used for I/O. + And error is returned if any is encountered. +*/ +@(require_results) +create :: proc(name: string) -> (^File, Error) { + return open(name, {.Read, .Write, .Create, .Trunc}, Permissions_Default_File) +} + +/* + `open` is a generalized open call, which defaults to opening for reading. + If the file does not exist, and the `{.Create}` flag is passed, it is created with the permissions `perm`, + and please note that the containing directory must exist otherwise and an error will be returned. + If successful, a `^File` is return which can be used for I/O. + And error is returned if any is encountered. +*/ +@(require_results) +open :: proc(name: string, flags := File_Flags{.Read}, perm := Permissions_Default) -> (^File, Error) { + return _open(name, flags, perm) +} + +// @(require_results) +// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { +// if buffer_size == 0 { +// return _open(name, flags, perm) +// } +// return _open_buffered(name, buffer_size, flags, perm) +// } + +/* + `new_file` returns a new `^File` with the given file descriptor `handle` and `name`. + The return value will only be `nil` IF the `handle` is not a valid file descriptor. +*/ +@(require_results) +new_file :: proc(handle: uintptr, name: string) -> ^File { + file, err := _new_file(handle, name, file_allocator()) + if err != nil { + panic(error_string(err)) + } + return file +} + +/* + `clone` returns a new `^File` based on the passed file `f` with the same underlying file descriptor. +*/ +@(require_results) +clone :: proc(f: ^File) -> (^File, Error) { + return _clone(f) +} + +/* + `fd` returns the file descriptor of the file `f` passed. If the file is not valid, an invalid handle will be returned. +*/ +@(require_results) +fd :: proc(f: ^File) -> uintptr { + return _fd(f) +} + +/* + `name` returns the name of the file. The lifetime of this string lasts as long as the file handle itself. +*/ +@(require_results) +name :: proc(f: ^File) -> string { + return _name(f) +} + +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ +close :: proc(f: ^File) -> Error { + if f != nil { + if f.stream.procedure == nil { + return .Unsupported + } + _, err := f.stream.procedure(f, .Close, nil, 0, nil, runtime.nil_allocator()) + return err + } + return nil +} + +/* + seek sets the offsets for the next read or write on a file to a specified `offset`, + according to what `whence` is set. + `.Start` is relative to the origin of the file. + `.Current` is relative to the current offset. + `.End` is relative to the end. + It returns the new offset and an error, if any is encountered. + Prefer `read_at` or `write_at` if the offset does not want to be changed. + +*/ +seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + return f.stream.procedure(f, .Seek, nil, offset, whence, runtime.nil_allocator()) + } + return 0, .Invalid_File +} + +/* + `read` reads up to len(p) bytes from the file `f`, and then stores them in `p`. + It returns the number of bytes read and an error, if any is encountered. + At the end of a file, it returns `0, io.EOF`. +*/ +read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Read, p, 0, nil, runtime.nil_allocator()) + return int(n64), err + } + return 0, .Invalid_File +} + +/* + `read_at` reads up to len(p) bytes from the file `f` at the byte offset `offset`, and then stores them in `p`. + It returns the number of bytes read and an error, if any is encountered. + `read_at` always returns a non-nil error when `n < len(p)`. + At the end of a file, the error is `io.EOF`. +*/ +read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Read_At, p, offset, nil, runtime.nil_allocator()) + return int(n64), err + } + return 0, .Invalid_File +} + +/* + `write` writes `len(p)` bytes from `p` to the file `f`. It returns the number of bytes written to + and an error, if any is encountered. + `write` returns a non-nil error when `n != len(p)`. +*/ +write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Write, p, 0, nil, runtime.nil_allocator()) + return int(n64), err + } + return 0, .Invalid_File +} + +/* + `write_at` writes `len(p)` bytes from `p` to the file `f` starting at byte offset `offset`. + It returns the number of bytes written to and an error, if any is encountered. + `write_at` returns a non-nil error when `n != len(p)`. +*/ +write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n64: i64 + n64, err = f.stream.procedure(f, .Write_At, p, offset, nil, runtime.nil_allocator()) + return int(n64), err + } + return 0, .Invalid_File +} + +/* + `file_size` returns the length of the file `f` in bytes and an error, if any is encountered. +*/ +file_size :: proc(f: ^File) -> (n: i64, err: Error) { + if f != nil { + if f.stream.procedure == nil { + return 0, .Unsupported + } + n, err = f.stream.procedure(f, .Size, nil, 0, nil, runtime.nil_allocator()) + if err == .Unsupported { + n = 0 + curr := seek(f, 0, .Current) or_return + end := seek(f, 0, .End) or_return + seek(f, curr, .Start) or_return + n = end + } + return + } + return 0, .Invalid_File +} + +/* + `flush` flushes a file `f` +*/ +flush :: proc(f: ^File) -> Error { + if f != nil { + if f.stream.procedure == nil { + return .Unsupported + } + _, err := f.stream.procedure(f, .Flush, nil, 0, nil, runtime.nil_allocator()) + return err + } + return nil +} + +/* + `sync` commits the current contents of the file `f` to stable storage. + This usually means flushing the file system's in-memory copy to disk. +*/ +sync :: proc(f: ^File) -> Error { + return _sync(f) +} + +/* + `truncate` changes the size of the file `f` to `size` in bytes. + This can be used to shorten or lengthen a file. + It does not change the "offset" of the file. +*/ +truncate :: proc(f: ^File, size: i64) -> Error { + return _truncate(f, size) +} + +/* + `remove` removes a named file or (empty) directory. +*/ +remove :: proc(name: string) -> Error { + return _remove(name) +} + +/* + `rename` renames (moves) `old_path` to `new_path`. +*/ +rename :: proc(old_path, new_path: string) -> Error { + return _rename(old_path, new_path) +} + +/* + `link` creates a `new_name` as a hard link to the `old_name` file. +*/ +link :: proc(old_name, new_name: string) -> Error { + return _link(old_name, new_name) +} + +/* + `symlink` creates a `new_name` as a symbolic link to the `old_name` file. +*/ +symlink :: proc(old_name, new_name: string) -> Error { + return _symlink(old_name, new_name) +} + +/* + `read_link` returns the destinction of the named symbolic link `name`. +*/ +read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) { + return _read_link(name,allocator) +} + + +chdir :: change_directory + +/* + Changes the current working directory to the named directory. +*/ +change_directory :: proc(name: string) -> Error { + return _chdir(name) +} + +chmod :: change_mode + +/* + Changes the mode/permissions of the named file to `mode`. + If the file is a symbolic link, it changes the mode of the link's target. + + On Windows, only `{.Write_User}` of `mode` is used, and controls whether or not + the file has a read-only attribute. Use `{.Read_User}` for a read-only file and + `{.Read_User, .Write_User}` for a readable & writable file. +*/ +change_mode :: proc(name: string, mode: Permissions) -> Error { + return _chmod(name, mode) +} + +chown :: change_owner + +/* + Changes the numeric `uid` and `gid` of a named file. If the file is a symbolic link, + it changes the `uid` and `gid` of the link's target. + + On Windows, it always returns an error. +*/ +change_owner :: proc(name: string, uid, gid: int) -> Error { + return _chown(name, uid, gid) +} + +fchdir :: fchange_directory + +/* + Changes the current working directory to the file, which must be a directory. +*/ +fchange_directory :: proc(f: ^File) -> Error { + return _fchdir(f) +} + +fchmod :: fchange_mode + +/* + Changes the current `mode` permissions of the file `f`. +*/ +fchange_mode :: proc(f: ^File, mode: Permissions) -> Error { + return _fchmod(f, mode) +} + +fchown :: fchange_owner + +/* + Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link, + it changes the `uid` and `gid` of the link's target. + + On Windows, it always returns an error. +*/ +fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { + return _fchown(f, uid, gid) +} + + +lchown :: change_owner_do_not_follow_links + +/* + Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link, + it changes the `uid` and `gid` of the lin itself. + + On Windows, it always returns an error. +*/ +change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { + return _lchown(name, uid, gid) +} + +chtimes :: change_times + +/* + Changes the access `atime` and modification `mtime` times of a named file. +*/ +change_times :: proc(name: string, atime, mtime: time.Time) -> Error { + return _chtimes(name, atime, mtime) +} + +fchtimes :: fchange_times + +/* + Changes the access `atime` and modification `mtime` times of the file `f`. +*/ +fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { + return _fchtimes(f, atime, mtime) +} + +/* + `exists` returns whether or not a named file exists. +*/ +@(require_results) +exists :: proc(path: string) -> bool { + return _exists(path) +} + +/* + `is_file` returns whether or not the type of a named file is a `File_Type.Regular` file. +*/ +@(require_results) +is_file :: proc(path: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) + if err != nil { + return false + } + return fi.type == .Regular +} + +is_dir :: is_directory + +/* + Returns whether or not the type of a named file is a `File_Type.Directory` file. +*/ +@(require_results) +is_directory :: proc(path: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) + if err != nil { + return false + } + return fi.type == .Directory +} + +/* + `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) + } else { + return _copy_file(dst_path, src_path) + } +} + +@(private) +_copy_file :: proc(dst_path, src_path: string) -> Error { + src := open(src_path) or_return + defer close(src) + + info := fstat(src, file_allocator()) or_return + defer file_info_delete(info, file_allocator()) + if info.type == .Directory { + return .Invalid_File + } + + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & Permissions_All) or_return + defer close(dst) + + _, err := io.copy(to_writer(dst), to_reader(src)) + return err +} \ No newline at end of file diff --git a/core/os/file_js.odin b/core/os/file_js.odin new file mode 100644 index 000000000..91ee7f02e --- /dev/null +++ b/core/os/file_js.odin @@ -0,0 +1,110 @@ +#+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: Permissions) -> (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 +} + +_is_tty :: proc "contextless" (f: ^File) -> bool { + return true +} + +_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: Permissions) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: Permissions) -> 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/file_linux.odin b/core/os/file_linux.odin new file mode 100644 index 000000000..f5f2ebdd7 --- /dev/null +++ b/core/os/file_linux.odin @@ -0,0 +1,560 @@ +#+private +package os2 + +import "base:runtime" +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, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + fd: linux.Fd, + allocator: runtime.Allocator, + + buffer: []byte, + rw_mutex: sync.RW_Mutex, // read write calls + p_mutex: sync.Mutex, // pread pwrite calls +} + +_stdin := File{ + stream = { + procedure = _file_stream_proc, + }, +} +_stdout := File{ + stream = { + procedure = _file_stream_proc, + }, +} +_stderr := File{ + stream = { + procedure = _file_stream_proc, + }, +} + +@init +_standard_stream_init :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: linux.Fd, name: string) -> ^File { + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = runtime.nil_allocator() + impl.name = name + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/proc/self/fd/0") + stdout = new_std(&files[1], 1, "/proc/self/fd/1") + stderr = new_std(&files[2], 2, "/proc/self/fd/2") +} + +_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + + // Just default to using O_NOCTTY because needing to open a controlling + // terminal would be incredibly rare. This has no effect on files while + // allowing us to open serial devices. + sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} + when size_of(rawptr) == 4 { + sys_flags += {.LARGEFILE} + } + switch flags & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: + case O_WRONLY: sys_flags += {.WRONLY} + case O_RDWR: sys_flags += {.RDWR} + } + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)transmute(u32)perm) + if errno != .NONE { + return nil, _get_platform_error(errno) + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + impl := new(File_Impl, allocator) or_return + defer if err != nil { + free(impl, allocator) + } + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = allocator + impl.name = _get_full_path(impl.fd, impl.allocator) or_return + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + return &impl.file, nil +} + +_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { + if f == nil || f.impl == nil { + return + } + + fd := (^File_Impl)(f.impl).fd + + clonefd, errno := linux.dup(fd) + if errno != nil { + err = _get_platform_error(errno) + return + } + defer if err != nil { linux.close(clonefd) } + + return _new_file(uintptr(clonefd), "", file_allocator()) +} + + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + f, err = _open(name, flags, perm) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.buffer = make([]byte, buffer_size, file_allocator()) + f.stream.procedure = _file_stream_buffered_proc + } + return +} + +_destroy :: proc(f: ^File_Impl) -> Error { + if f == nil { + return nil + } + a := f.allocator + err0 := delete(f.name, a) + err1 := delete(f.buffer, a) + err2 := free(f, a) + err0 or_return + err1 or_return + err2 or_return + return nil +} + + +_close :: proc(f: ^File_Impl) -> Error { + if f == nil{ + return nil + } + errno := linux.close(f.fd) + if errno == .EBADF { // avoid possible double free + return _get_platform_error(errno) + } + _destroy(f) + return _get_platform_error(errno) +} + +_fd :: proc(f: ^File) -> uintptr { + if f == nil || f.impl == nil { + return ~uintptr(0) + } + impl := (^File_Impl)(f.impl) + 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 "" +} + +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + // We have to handle this here, because Linux returns EINVAL for both + // invalid offsets and invalid whences. + switch whence { + case .Start, .Current, .End: + break + case: + return 0, .Invalid_Whence + } + n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) + #partial switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case .NONE: + return n, nil + case: + return 0, _get_platform_error(errno) + } +} + +_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { + if len(p) <= 0 { + return 0, nil + } + + n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)]) + if errno != .NONE { + return 0, _get_platform_error(errno) + } + return i64(n), io.Error.EOF if n == 0 else nil +} + +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) <= 0 { + return 0, nil + } + if offset < 0 { + return 0, .Invalid_Offset + } + n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset) + if errno != .NONE { + return 0, _get_platform_error(errno) + } + if n == 0 { + return 0, .EOF + } + return i64(n), nil +} + +_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) { + p := p + for len(p) > 0 { + n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) + } + + return +} + +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + p := p + offset := offset + for len(p) > 0 { + n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) + offset += i64(n) + } + + return +} + +@(no_sanitize_memory) +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { + // TODO: Identify 0-sized "pseudo" files and return No_Size. This would + // eliminate the need for the _read_entire_pseudo_file procs. + s: linux.Stat = --- + errno := linux.fstat(f.fd, &s) + if errno != .NONE { + return 0, _get_platform_error(errno) + } + + if s.mode & linux.S_IFMT == linux.S_IFREG { + return i64(s.size), nil + } + return 0, .No_Size +} + +_sync :: proc(f: ^File) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fsync(impl.fd)) +} + +_flush :: proc(f: ^File_Impl) -> Error { + return _get_platform_error(linux.fsync(f.fd)) +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.ftruncate(impl.fd, size)) +} + +_remove :: proc(name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + + if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { + linux.close(fd) + return _get_platform_error(linux.rmdir(name_cstr)) + } + + return _get_platform_error(linux.unlink(name_cstr)) +} + +_rename :: proc(old_name, new_name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return + + return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) +} + +_link :: proc(old_name, new_name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return + + return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return + return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) +} + +_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) { + bufsz : uint = 256 + buf := make([]byte, bufsz, allocator) + for { + sz, errno := linux.readlink(name_cstr, buf[:]) + if errno != .NONE { + delete(buf, allocator) + return "", _get_platform_error(errno) + } else if sz == int(bufsz) { + bufsz *= 2 + delete(buf, allocator) + buf = make([]byte, bufsz, allocator) + } else { + return string(buf[:sz]), nil + } + } +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _read_link_cstr(name_cstr, allocator) +} + +_chdir :: proc(name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _get_platform_error(linux.chdir(name_cstr)) +} + +_fchdir :: proc(f: ^File) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchdir(impl.fd)) +} + +_chmod :: proc(name: string, mode: Permissions) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)transmute(u32)mode)) +} + +_fchmod :: proc(f: ^File, mode: Permissions) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)transmute(u32)mode)) +} + +// NOTE: will throw error without super user priviledges +_chown :: proc(name: string, uid, gid: int) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) +} + +// NOTE: will throw error without super user priviledges +_lchown :: proc(name: string, uid, gid: int) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) +} + +// NOTE: will throw error without super user priviledges +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, + } + return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, ×[0], nil)) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]linux.Time_Spec { + { + uint(atime._nsec) / uint(time.Second), + uint(atime._nsec) % uint(time.Second), + }, + { + uint(mtime._nsec) / uint(time.Second), + uint(mtime._nsec) % uint(time.Second), + }, + } + impl := (^File_Impl)(f.impl) + return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) +} + +_exists :: proc(name: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr, _ := clone_to_cstring(name, temp_allocator) + return linux.access(name_cstr, linux.F_OK) == .NONE +} + +/* For reading Linux system files that stat to size 0 */ +_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } + +_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + return _read_entire_pseudo_file_cstring(name_cstr, allocator) +} + +_read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Allocator) -> ([]u8, Error) { + fd, errno := linux.open(name, {}) + if errno != .NONE { + return nil, _get_platform_error(errno) + } + defer linux.close(fd) + + BUF_SIZE_STEP :: 128 + contents := make([dynamic]u8, 0, BUF_SIZE_STEP, allocator) + + n: int + i: int + for { + resize(&contents, i + BUF_SIZE_STEP) + n, errno = linux.read(fd, contents[i:i+BUF_SIZE_STEP]) + if errno != .NONE { + delete(contents) + return nil, _get_platform_error(errno) + } + if n < BUF_SIZE_STEP { + break + } + i += BUF_SIZE_STEP + } + + resize(&contents, i + n) + return contents[:], nil +} + +@(private="package") +_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) + switch mode { + case .Read: + n, err = _read(f, p) + return + case .Read_At: + n, err = _read_at(f, p, offset) + return + case .Write: + n, err = _write(f, p) + return + case .Write_At: + n, err = _write_at(f, p, offset) + return + case .Seek: + n, err = _seek(f, offset, whence) + return + case .Size: + n, err = _file_size(f) + return + case .Flush: + err = _flush(f) + return + case .Close, .Destroy: + err = _close(f) + return + 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 + } + return 0, .Unsupported +} + + +@(private="package") +_file_stream_buffered_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) + switch mode { + case .Read: + n, err = _read(f, p) + return + case .Read_At: + n, err = _read_at(f, p, offset) + return + case .Write: + n, err = _write(f, p) + return + case .Write_At: + n, err = _write_at(f, p, offset) + return + case .Seek: + n, err = _seek(f, offset, whence) + return + case .Size: + n, err = _file_size(f) + return + case .Flush: + err = _flush(f) + return + case .Close, .Destroy: + err = _close(f) + return + 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 + } + return 0, .Unsupported +} + diff --git a/core/os/file_posix.odin b/core/os/file_posix.odin new file mode 100644 index 000000000..ef53bf116 --- /dev/null +++ b/core/os/file_posix.odin @@ -0,0 +1,514 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:io" +import "core:c" +import "core:time" +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, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + cname: cstring, + fd: posix.FD, + allocator: runtime.Allocator, +} + +@(init) +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: posix.FD, name: cstring) -> ^File { + impl.file.impl = impl + impl.fd = fd + impl.allocator = runtime.nil_allocator() + impl.cname = name + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], posix.STDIN_FILENO, "/dev/stdin") + stdout = new_std(&files[1], posix.STDOUT_FILENO, "/dev/stdout") + stderr = new_std(&files[2], posix.STDERR_FILENO, "/dev/stderr") +} + +_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC} + + if .Write in flags { + if .Read in flags { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + + fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(transmute(u32)perm)) + if fd < 0 { + err = _get_platform_error() + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } else if handle == ~uintptr(0) { + err = .Invalid_File + return + } + + crname := _posix_absolute_path(posix.FD(handle), name, allocator) or_return + rname := string(crname) + + f = __new_file(posix.FD(handle), allocator) + impl := (^File_Impl)(f.impl) + impl.name = rname + impl.cname = crname + + return f, nil +} + +__new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File { + impl := new(File_Impl, allocator) + impl.file.impl = impl + impl.fd = posix.FD(handle) + impl.allocator = allocator + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + return &impl.file +} + +_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_Pointer + return + } + + impl := (^File_Impl)(f.impl) + + fd := posix.dup(impl.fd) + if fd <= 0 { + err = _get_platform_error() + return + } + defer if err != nil { posix.close(fd) } + + clone = __new_file(fd, file_allocator()) + clone_impl := (^File_Impl)(clone.impl) + clone_impl.cname = clone_to_cstring(impl.name, file_allocator()) or_return + clone_impl.name = string(clone_impl.cname) + + return +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if f == nil { return nil } + + if posix.close(f.fd) != .OK { + err = _get_platform_error() + } + + allocator := f.allocator + + delete(f.cname, allocator) + free(f, allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> posix.FD { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_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) +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + if posix.fsync(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.remove(cname) != 0 { + return _get_platform_error() + } + return nil +} + +_rename :: proc(old_path, new_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_path, temp_allocator) or_return + cnew := clone_to_cstring(new_path, temp_allocator) or_return + if posix.rename(cold, cnew) != 0 { + return _get_platform_error() + } + return nil +} + +_link :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return + if posix.link(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_symlink :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return + if posix.symlink(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return + + buf: [dynamic]byte + buf.allocator = allocator + defer if err != nil { delete(buf) } + + // Loop this because the file might've grown between lstat() and readlink(). + for { + stat: posix.stat_t + if posix.lstat(cname, &stat) != .OK { + err = _get_platform_error() + return + } + + bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX) + + if bufsiz == len(buf) { + bufsiz *= 2 + } + + // Overflow. + if bufsiz <= 0 { + err = Platform_Error(posix.Errno.E2BIG) + return + } + + resize(&buf, bufsiz) or_return + + size := posix.readlink(cname, raw_data(buf), uint(bufsiz)) + if size < 0 { + err = _get_platform_error() + return + } + + // File has probably grown between lstat() and readlink(). + if size == bufsiz { + continue + } + + s = string(buf[:size]) + return + } +} + +_chdir :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.chdir(cname) != .OK { + return _get_platform_error() + } + return nil +} + +_fchdir :: proc(f: ^File) -> Error { + if posix.fchdir(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: Permissions) -> Error { + if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_chmod :: proc(name: string, mode: Permissions) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chown :: proc(name: string, uid, gid: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) { + times := [2]posix.timeval{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */ + }, + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + + if posix.utimes(cname, ×) != .OK { + return _get_platform_error() + } + return nil +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]posix.timespec{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_nsec = c.long(atime._nsec%1e9), /* nanoseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_nsec = c.long(mtime._nsec%1e9), /* nanoseconds */ + }, + } + + if posix.futimens(__fd(f), ×) != .OK { + return _get_platform_error() + } + return nil +} + +_exists :: proc(path: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath, err := clone_to_cstring(path, temp_allocator) + if err != nil { return false } + return posix.access(cpath) == .OK +} + +_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 + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := uint(min(len(p), MAX_RW)) + _n := i64(posix.read(fd, raw_data(p), to_read)) + switch { + case _n == 0: + err = .EOF + case _n < 0: + err = .Unknown + case: + n = _n + } + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := uint(min(len(p), MAX_RW)) + _n := i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset))) + switch { + case _n == 0: + err = .EOF + case _n < 0: + err = .Unknown + case: + n = _n + } + return + + case .Write: + p := p + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.write(fd, raw_data(p), to_write)); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + } + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + offset += _n + } + } + return + + case .Seek: + #assert(int(posix.Whence.SET) == int(io.Seek_From.Start)) + #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + _n := i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) + if _n < 0 { + #partial switch posix.get_errno() { + case .EINVAL: + err = .Invalid_Offset + case: + err = .Unknown + } + return + } + + n = _n + return + + case .Size: + stat: posix.stat_t + if posix.fstat(fd, &stat) != .OK { + err = .Unknown + return + } + + n = i64(stat.st_size) + return + + case .Flush: + err = _sync(&f.file) + return + + case .Close, .Destroy: + err = _close(f) + return + + 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 + } +} diff --git a/core/os/file_posix_darwin.odin b/core/os/file_posix_darwin.odin new file mode 100644 index 000000000..521fb345b --- /dev/null +++ b/core/os/file_posix_darwin.odin @@ -0,0 +1,46 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 50 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} + +_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src_path, temp_allocator) or_return + cdst := clone_to_cstring(dst_path, temp_allocator) or_return + + // Disallow directories, as specified by the generic implementation. + + stat: posix.stat_t + if posix.stat(csrc, &stat) != .OK { + err = _get_platform_error() + return + } + + if posix.S_ISDIR(stat.st_mode) { + err = .Invalid_File + return + } + + ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL) + if ret < 0 { + err = _get_platform_error() + } + + return +} \ No newline at end of file diff --git a/core/os/file_posix_freebsd.odin b/core/os/file_posix_freebsd.odin new file mode 100644 index 000000000..05d031930 --- /dev/null +++ b/core/os/file_posix_freebsd.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:c" +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [posix.PATH_MAX]c.char, + } + + F_KINFO :: 22 + + kinfo: KInfo_File + kinfo.structsize = size_of(KInfo_File) + + res := posix.fcntl(fd, posix.FCNTL_Cmd(F_KINFO), &kinfo) + if res == -1 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&kinfo.path[0])), allocator) +} diff --git a/core/os/file_posix_netbsd.odin b/core/os/file_posix_netbsd.odin new file mode 100644 index 000000000..f96c227ba --- /dev/null +++ b/core/os/file_posix_netbsd.odin @@ -0,0 +1,18 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 15 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/file_posix_other.odin b/core/os/file_posix_other.odin new file mode 100644 index 000000000..8871a0062 --- /dev/null +++ b/core/os/file_posix_other.odin @@ -0,0 +1,21 @@ +#+private +#+build openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return + + buf: [posix.PATH_MAX]byte + path = posix.realpath(cname, raw_data(buf[:])) + if path == nil { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(path), allocator) +} diff --git a/core/os/file_stream.odin b/core/os/file_stream.odin new file mode 100644 index 000000000..af6e50921 --- /dev/null +++ b/core/os/file_stream.odin @@ -0,0 +1,89 @@ +package os2 + +import "base:intrinsics" +import "base:runtime" +import "core:io" + +// A subset of the io.Stream_Mode with added File specific modes +File_Stream_Mode :: enum { + Close, + Flush, + Read, + Read_At, + Write, + Write_At, + Seek, + Size, + Destroy, + Query, // query what modes are available on `io.Stream` + + Fstat, // File specific (not available on io.Stream) +} +#assert(intrinsics.type_is_superset_of(File_Stream_Mode, io.Stream_Mode)) + +// Superset interface of io.Stream_Proc with the added `runtime.Allocator` parameter needed for the Fstat mode +File_Stream_Proc :: #type proc( + stream_data: rawptr, + mode: File_Stream_Mode, + p: []byte, + offset: i64, + whence: io.Seek_From, + allocator: runtime.Allocator, +) -> (n: i64, err: Error) + +File_Stream :: struct { + procedure: File_Stream_Proc, + data: rawptr, +} + + +// Converts a file `f` into an `io.Stream` +to_stream :: proc(f: ^File) -> (s: io.Stream) { + if f != nil { + assert(f.stream.procedure != nil) + s = { + file_io_stream_proc, + f, + } + } + return +} + +/* + This is an alias of `to_stream` which converts a file `f` to an `io.Stream`. + It can be useful to indicate what the stream is meant to be used for as a writer, + even if it has no logical difference. +*/ +to_writer :: to_stream + +/* + This is an alias of `to_stream` which converts a file `f` to an `io.Stream`. + It can be useful to indicate what the stream is meant to be used for as a reader, + even if it has no logical difference. +*/ +to_reader :: to_stream + + +@(private="package") +file_io_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File)(stream_data) + + file_stream_mode := transmute(File_Stream_Mode)mode + + ferr: Error + n, ferr = f.stream.procedure(f, file_stream_mode, p, offset, whence, runtime.nil_allocator()) + err = error_to_io_error(ferr) + return +} + +@(private="package") +file_stream_fstat_utility :: proc(f: ^File_Impl, p: []byte, allocator: runtime.Allocator) -> (err: Error) { + fi: File_Info + if len(p) >= size_of(fi) { + fi, err = _fstat(&f.file, allocator) + runtime.mem_copy_non_overlapping(raw_data(p), &fi, size_of(fi)) + } else { + err = .Short_Buffer + } + return +} \ No newline at end of file diff --git a/core/os/file_util.odin b/core/os/file_util.odin new file mode 100644 index 000000000..f81dc2190 --- /dev/null +++ b/core/os/file_util.odin @@ -0,0 +1,257 @@ +package os2 + +import "base:runtime" +import "core:strconv" +import "core:unicode/utf8" + +/* + `write_string` writes a string `s` to file `f`. + Returns the number of bytes written and an error, if any is encountered. +*/ +write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { + return write(f, transmute([]byte)s) +} + +/* + `write_strings` writes a variadic list of strings `strings` to file `f`. + Returns the number of bytes written and an error, if any is encountered. +*/ +write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) { + for s in strings { + m: int + m, err = write_string(f, s) + n += m + if err != nil { + return + } + } + return +} +/* + `write_byte` writes a byte `b` to file `f`. + Returns the number of bytes written and an error, if any is encountered. +*/ +write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { + return write(f, []byte{b}) +} + +/* + `write_rune` writes a rune `r` as an UTF-8 encoded string to file `f`. + Returns the number of bytes written and an error, if any is encountered. +*/ +write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { + if r < utf8.RUNE_SELF { + return write_byte(f, byte(r)) + } + + b: [4]byte + b, n = utf8.encode_rune(r) + return write(f, b[:n]) +} + +/* + `write_encoded_rune` writes a rune `r` as an UTF-8 encoded string which with escaped control codes to file `f`. + Returns the number of bytes written and an error, if any is encountered. +*/ +write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { + wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { + n^ += m + if merr != nil { + err^ = merr + return true + } + return false + } + + if wrap(write_byte(f, '\''), &n, &err) { return } + + switch r { + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } + case: + if r < 32 { + if wrap(write_string(f, "\\x"), &n, &err) { return } + b: [2]byte + s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) + switch len(s) { + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } + } + } else { + if wrap(write_rune(f, r), &n, &err) { return } + } + } + _ = wrap(write_byte(f, '\''), &n, &err) + return +} + +/* + `write_ptr` is a utility procedure that writes the bytes points at `data` with length `len`. + + It is equivalent to: `write(f, ([^]byte)(data)[:len])` +*/ +write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { + return write(f, ([^]byte)(data)[:len]) +} + +/* + `read_ptr` is a utility procedure that reads the bytes points at `data` with length `len`. + + It is equivalent to: `read(f, ([^]byte)(data)[:len])` +*/ +read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { + return read(f, ([^]byte)(data)[:len]) +} + + + +/* + `read_at_least` reads from `f` into `buf` until it has read at least `min` bytes. + It returns the number of bytes copied and an error if fewer bytes were read. + The error is only an `io.EOF` if no bytes were read. +*/ +read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, .Short_Buffer + } + nn := max(int) + for nn > 0 && n < min && err == nil { + nn, err = read(f, buf[n:]) + n += nn + } + if n >= min { + err = nil + } + return +} + +/* + `read_full` reads exactly `len(buf)` bytes from `f` into `buf`. + It returns the number of bytes copied and an error if fewer bytes were read. + The error is only an `io.EOF` if no bytes were read. + + It is equivalent to `read_at_least(f, buf, len(buf))`. +*/ +read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) { + return read_at_least(f, buf, len(buf)) +} + + + +read_entire_file :: proc{ + read_entire_file_from_path, + read_entire_file_from_file, +} + +/* + `read_entire_file_from_path` reads the entire named file `name` into memory allocated with `allocator`. + A slice of bytes and an error is returned, if any error is encountered. +*/ +@(require_results) +read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) { + f, ferr := open(name) + if ferr != nil { + return nil, ferr + } + defer close(f) + return read_entire_file_from_file(f=f, allocator=allocator, loc=loc) +} + +/* + `read_entire_file_from_file` reads the entire file `f` into memory allocated with `allocator`. + A slice of bytes and an error is returned, if any error is encountered. +*/ +@(require_results) +read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) { + size: int + has_size := false + if size64, serr := file_size(f); serr == nil { + if i64(int(size64)) == size64 { + has_size = true + size = int(size64) + } + } + + if has_size && size > 0 { + total: int + data = make([]byte, size, allocator, loc) or_return + for total < len(data) { + n: int + n, err = read(f, data[total:]) + total += n + if err != nil { + if err == .EOF { + err = nil + } + data = data[:total] + break + } + } + return + } else { + buffer: [1024]u8 + out_buffer := make([dynamic]u8, 0, 0, allocator, loc) + total := 0 + for { + n: int + n, err = read(f, buffer[:]) + total += n + append_elems(&out_buffer, ..buffer[:n], loc=loc) or_return + if err != nil { + if err == .EOF || err == .Broken_Pipe { + err = nil + } + data = out_buffer[:total] + return + } + } + } +} + +/* + `write_entire_file` writes the contents of `data` into named file `name`. + It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. + An error is returned if any is encountered. +*/ +write_entire_file :: proc{ + write_entire_file_from_bytes, + write_entire_file_from_string, +} + +/* + `write_entire_file_from_bytes` writes the contents of `data` into named file `name`. + It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. + An error is returned if any is encountered. +*/ +@(require_results) +write_entire_file_from_bytes :: proc(name: string, data: []byte, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error { + flags := O_WRONLY|O_CREATE + if truncate { + flags |= O_TRUNC + } + f := open(name, flags, perm) or_return + _, err := write(f, data) + if cerr := close(f); cerr != nil && err == nil { + err = cerr + } + return err +} + + + +/* + `write_entire_file_from_string` writes the contents of `data` into named file `name`. + It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. + An error is returned if any is encountered. +*/ +@(require_results) +write_entire_file_from_string :: proc(name: string, data: string, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error { + return write_entire_file(name, transmute([]byte)data, perm, truncate) +} diff --git a/core/os/file_wasi.odin b/core/os/file_wasi.odin new file mode 100644 index 000000000..78aa90699 --- /dev/null +++ b/core/os/file_wasi.odin @@ -0,0 +1,570 @@ +#+feature global-context +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:sys/wasm/wasi" +import "core:time" + +// NOTE: Don't know if there is a max in wasi. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + fd: wasi.fd_t, + allocator: runtime.Allocator, +} + +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +preopens: []Preopen + +@(init) +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { + impl.file.impl = impl + impl.allocator = runtime.nil_allocator() + impl.fd = fd + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/dev/stdin") + stdout = new_std(&files[1], 1, "/dev/stdout") + stderr = new_std(&files[2], 2, "/dev/stderr") +} + +@(init) +init_preopens :: proc "contextless" () { + strip_prefixes :: proc "contextless" (path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + context = runtime.default_context() + + n: int + n_loop: for fd := wasi.fd_t(3); ; fd += 1 { + _, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break n_loop + case .SUCCESS: n += 1 + case: + print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get") + break n_loop + } + } + + alloc_err: runtime.Allocator_Error + preopens, alloc_err = make([]Preopen, n, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopens") + return + } + + loop: for &preopen, i in preopens { + fd := wasi.fd_t(3 + i) + + desc, err := wasi.fd_prestat_get(fd) + assert(err == .SUCCESS) + + switch desc.tag { + case .DIR: + buf: []byte + buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name") + continue loop + } + + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name") + continue loop + } + + preopen.fd = fd + preopen.prefix = strip_prefixes(string(buf)) + } + } +} + +@(require_results) +match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + if path == "" { + return 0, "", false + } + + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} + +_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return nil, .Invalid_Path + } + + oflags: wasi.oflags_t + if .Create in flags { oflags += {.CREATE} } + if .Excl in flags { oflags += {.EXCL} } + if .Trunc in flags { oflags += {.TRUNC} } + + fdflags: wasi.fdflags_t + if .Append in flags { fdflags += {.APPEND} } + if .Sync in flags { fdflags += {.SYNC} } + if .Non_Blocking in flags { fdflags += {.NONBLOCK} } + + // NOTE: rights are adjusted to what this package's functions might want to call. + rights: wasi.rights_t + if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} } + if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} } + + fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + impl := new(File_Impl, allocator) or_return + defer if err != nil { free(impl, allocator) } + + impl.allocator = allocator + // NOTE: wasi doesn't really do full paths afact. + impl.name = clone_string(name, allocator) or_return + impl.fd = wasi.fd_t(handle) + impl.file.impl = impl + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + + return &impl.file, nil +} + +_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { + if f == nil || f.impl == nil { + return + } + + dir_fd, relative, ok := match_preopen(name(f)) + if !ok { + return nil, .Invalid_Path + } + + fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, {}, {}, {}, {}) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + defer if err != nil { wasi.fd_close(fd) } + + fderr = wasi.fd_renumber((^File_Impl)(f.impl).fd, fd) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + + return _new_file(uintptr(fd), name(f), file_allocator()) +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if errno := wasi.fd_close(f.fd); errno != nil { + err = _get_platform_error(errno) + } + + delete(f.name, f.allocator) + free(f, f.allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> wasi.fd_t { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + 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 + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + return _get_platform_error(wasi.fd_sync(__fd(f))) +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size))) +} + +_remove :: proc(name: string) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + err := wasi.path_remove_directory(dir_fd, relative) + if err == .NOTDIR { + err = wasi.path_unlink_file(dir_fd, relative) + } + + return _get_platform_error(err) +} + +_rename :: proc(old_path, new_path: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_path) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_path) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative)) +} + +_link :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + if src_dir_fd != new_dir_fd { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative)) +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return "", .Invalid_Path + } + + n, _err := wasi.path_readlink(dir_fd, relative, nil) + if _err != nil { + err = _get_platform_error(_err) + return + } + + buf := make([]byte, n, allocator) or_return + + _, _err = wasi.path_readlink(dir_fd, relative, buf) + s = string(buf) + err = _get_platform_error(_err) + return +} + +_chdir :: proc(name: string) -> Error { + return .Unsupported +} + +_fchdir :: proc(f: ^File) -> Error { + return .Unsupported +} + +_fchmod :: proc(f: ^File, mode: Permissions) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: Permissions) -> 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 { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM})) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM})) +} + +_exists :: proc(path: string) -> bool { + dir_fd, relative, ok := match_preopen(path) + if !ok { + return false + } + + _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if err != nil { + return false + } + + return true +} + +_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 + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_read(fd, {p[:to_read]}) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset)) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Write: + p := p + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_write(fd, {p[:to_write]}) + if _err != nil { + err = .Unknown + return + } + p = p[_n:] + n += i64(_n) + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset)) + if _err != nil { + err = .Unknown + return + } + + p = p[_n:] + n += i64(_n) + offset += i64(_n) + } + return + + case .Seek: + #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start)) + #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current)) + #assert(int(wasi.whence_t.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence)) + #partial switch _err { + case .INVAL: + err = .Invalid_Offset + case: + err = .Unknown + case .SUCCESS: + n = i64(_n) + } + return + + case .Size: + stat, _err := wasi.fd_filestat_get(fd) + if _err != nil { + err = .Unknown + return + } + + n = i64(stat.size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + 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 + } +} diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin new file mode 100644 index 000000000..0e3448dd7 --- /dev/null +++ b/core/os/file_windows.odin @@ -0,0 +1,995 @@ +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:mem" +import "core:sync" +import "core:time" +import "core:unicode/utf16" +import win32 "core:sys/windows" + +INVALID_HANDLE :: ~uintptr(0) + +_ERROR_BAD_NETPATH :: 53 +MAX_RW :: 1<<30 + + +File_Impl_Kind :: enum u8 { + File, + Console, + Pipe, +} + +File_Impl :: struct { + file: File, + + fd: rawptr, + name: string, + wname: win32.wstring, + kind: File_Impl_Kind, + + allocator: runtime.Allocator, + + r_buf: []byte, + w_buf: []byte, + w_n: int, + max_consecutive_empty_writes: int, + + rw_mutex: sync.RW_Mutex, // read write calls + p_mutex: sync.Mutex, // pread pwrite calls +} + +@(init) +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, code: u32, name: string) -> ^File { + impl.file.impl = impl + + impl.allocator = runtime.nil_allocator() + impl.fd = win32.GetStdHandle(code) + impl.name = name + impl.wname = nil + + handle := _handle(&impl.file) + kind := File_Impl_Kind.File + if m: u32; win32.GetConsoleMode(handle, &m) { + kind = .Console + } + if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { + kind = .Pipe + } + impl.kind = kind + + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], win32.STD_INPUT_HANDLE, "") + stdout = new_std(&files[1], win32.STD_OUTPUT_HANDLE, "") + stderr = new_std(&files[2], win32.STD_ERROR_HANDLE, "") +} + +_handle :: proc "contextless" (f: ^File) -> win32.HANDLE { + return win32.HANDLE(_fd(f)) +} + +_open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (handle: uintptr, err: Error) { + if len(name) == 0 { + err = .Not_Exist + return + } + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + path := _fix_long_path(name, temp_allocator) or_return + access: u32 + switch flags & {.Read, .Write} { + case {.Read}: access = win32.FILE_GENERIC_READ + case {.Write}: access = win32.FILE_GENERIC_WRITE + case {.Read, .Write}: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE + } + + if .Create in flags { + access |= win32.FILE_GENERIC_WRITE + } + if .Append in flags { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = .Inheritable in flags, + } + + create_mode: u32 = win32.OPEN_EXISTING + switch { + case flags & {.Create, .Excl} == {.Create, .Excl}: + create_mode = win32.CREATE_NEW + case flags & {.Create, .Trunc} == {.Create, .Trunc}: + create_mode = win32.CREATE_ALWAYS + case flags & {.Create} == {.Create}: + create_mode = win32.OPEN_ALWAYS + case flags & {.Trunc} == {.Trunc}: + create_mode = win32.TRUNCATE_EXISTING + } + + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS + if .Write_User not_in perm { + attrs = win32.FILE_ATTRIBUTE_READONLY + if create_mode == win32.CREATE_ALWAYS { + // NOTE(bill): Open has just asked to create a file in read-only mode. + // If the file already exists, to make it akin to a *nix open call, + // the call preserves the existing permissions. + nix_attrs := win32.FILE_ATTRIBUTE_NORMAL + if .Non_Blocking in flags { + nix_attrs |= win32.FILE_FLAG_OVERLAPPED + } + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, nix_attrs, nil) + if h == win32.INVALID_HANDLE { + switch e := win32.GetLastError(); e { + case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: + // file does not exist, create the file + case 0: + return uintptr(h), nil + case: + return 0, _get_platform_error() + } + } + } + } + + if .Non_Blocking in flags { + attrs |= win32.FILE_FLAG_OVERLAPPED + } + + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) + if h == win32.INVALID_HANDLE { + return 0, _get_platform_error() + } + return uintptr(h), nil +} + + +_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags, perm) or_return + return _new_file(handle, name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if handle == INVALID_HANDLE { + return + } + impl := new(File_Impl, allocator) or_return + defer if err != nil { + free(impl, allocator) + } + + impl.file.impl = impl + + impl.allocator = allocator + impl.fd = rawptr(handle) + impl.name = clone_string(name, impl.allocator) or_return + impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return + + handle := _handle(&impl.file) + kind := File_Impl_Kind.File + if m: u32; win32.GetConsoleMode(handle, &m) { + kind = .Console + } + if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { + kind = .Pipe + } + impl.kind = kind + + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + + return &impl.file, nil +} + + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags, perm) or_return + return _new_file_buffered(handle, name, buffer_size) +} + +_new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) -> (f: ^File, err: Error) { + f, err = _new_file(handle, name, file_allocator()) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.r_buf = make([]byte, buffer_size, file_allocator()) + impl.w_buf = make([]byte, buffer_size, file_allocator()) + } + return +} + +_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { + if f == nil || f.impl == nil { + return + } + + clonefd: win32.HANDLE + process := win32.GetCurrentProcess() + if !win32.DuplicateHandle( + process, + win32.HANDLE(_fd(f)), + process, + &clonefd, + 0, + false, + win32.DUPLICATE_SAME_ACCESS, + ) { + err = _get_platform_error() + return + } + defer if err != nil { win32.CloseHandle(clonefd) } + + return _new_file(uintptr(clonefd), name(f), file_allocator()) +} + +_fd :: proc "contextless" (f: ^File) -> uintptr { + if f == nil || f.impl == nil { + return INVALID_HANDLE + } + 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 + } + + a := f.allocator + err0 := free(rawptr(f.wname), a) + err1 := delete(f.name, a) + err2 := delete(f.r_buf, a) + err3 := delete(f.w_buf, a) + err4 := free(f, a) + err0 or_return + err1 or_return + err2 or_return + err3 or_return + err4 or_return + return nil +} + + +_close :: proc(f: ^File_Impl) -> Error { + if f == nil { + return nil + } + if !win32.CloseHandle(win32.HANDLE(f.fd)) { + return .Closed + } + return _destroy(f) +} + +_name :: proc(f: ^File) -> string { + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" +} + +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + handle := _handle(&f.file) + if handle == win32.INVALID_HANDLE { + return 0, .Invalid_File + } + + if f.kind == .Pipe { + return 0, .Invalid_File + } + + sync.guard(&f.rw_mutex) + + w: u32 + switch whence { + case .Start: w = win32.FILE_BEGIN + case .Current: w = win32.FILE_CURRENT + case .End: w = win32.FILE_END + case: + return 0, .Invalid_Whence + } + hi := i32(offset>>32) + lo := i32(offset) + + dw_ptr := win32.SetFilePointer(handle, lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + return 0, _get_platform_error() + } + return i64(hi)<<32 + i64(dw_ptr), nil +} + +_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _read_internal(f, p) +} + +_read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + length := len(p) + if length == 0 { + return + } + + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } + + // TODO(bill): should this be moved to `File_Impl` instead? + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 + + for n < len(b) && err == nil { + min_read := max(len(b)/4, 1 if len(b) > 0 else 0) + max_read := u32(min(BUF_SIZE, min_read)) + if max_read == 0 { + break + } + + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = _get_platform_error() + } + + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] + + ctrl_z := false + for i := 0; i < len(src) && n+i < len(b); i += 1 { + x := src[i] + if x == 0x1a { // ctrl-z + ctrl_z = true + break + } + b[n] = x + n += 1 + } + if ctrl_z || single_read_length < max_read { + break + } + + // NOTE(bill): if the last two values were a newline, then it is expected that + // this is the end of the input + if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { + break + } + } + + return + } + + handle := _handle(&f.file) + + total_read: int + + sync.shared_guard(&f.rw_mutex) // multiple readers + + if sync.guard(&f.p_mutex) { + to_read := win32.DWORD(min(length, MAX_RW)) + switch f.kind { + case .Console: + // NOTE(laytan): at least for now, just use ReadFile, it seems to work fine, + // but, there may be issues with certain situations that we need to get reproductions for. + // total_read, err = read_console(handle, p[total_read:][:to_read]) + fallthrough + case .Pipe, .File: + single_read_length: win32.DWORD + ok := win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) + if ok { + total_read += int(single_read_length) + } else { + err = _get_platform_error() + } + } + } + + if total_read == 0 && err == nil { + // ok and 0 bytes means EOF: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + err = .EOF + } + + return i64(total_read), err +} + +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + + } + curr_offset := _seek(f, 0, .Current) or_return + defer _seek(f, curr_offset, .Start) + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := _handle(&f.file) + done: win32.DWORD + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = i64(done) + return + } + + sync.guard(&f.p_mutex) + + p, offset := p, offset + for len(p) > 0 { + m := pread(f, p, offset) or_return + n += m + p = p[m:] + offset += i64(m) + } + return +} + +_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _write_internal(f, p) +} +_write_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + if len(p) == 0 { + return + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(p)) + + handle := _handle(&f.file) + + sync.guard(&f.rw_mutex) + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + n = i64(total_write) + err = _get_platform_error() + return + } + total_write += i64(single_write_length) + } + return i64(total_write), nil +} + +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + + } + curr_offset := _seek(f, 0, .Current) or_return + defer _seek(f, curr_offset, .Start) + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + h := _handle(&f.file) + done: win32.DWORD + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = i64(done) + return + } + + sync.guard(&f.p_mutex) + p, offset := p, offset + for len(p) > 0 { + m := pwrite(f, p, offset) or_return + n += m + p = p[m:] + offset += i64(m) + } + return +} + +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { + length: win32.LARGE_INTEGER + handle := _handle(&f.file) + if f.kind == .Pipe { + bytes_available: u32 + if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { + return i64(bytes_available), nil + } else { + err = _get_platform_error() + return + } + } + if !win32.GetFileSizeEx(handle, &length) { + err = _get_platform_error() + } + n = i64(length) + return +} + + +_sync :: proc(f: ^File) -> Error { + if f != nil && f.impl != nil { + return _flush_internal((^File_Impl)(f.impl)) + } + return nil +} + +_flush :: proc(f: ^File_Impl) -> Error { + return _flush_internal(f) +} +_flush_internal :: proc(f: ^File_Impl) -> Error { + handle := _handle(&f.file) + if !win32.FlushFileBuffers(handle) { + return _get_platform_error() + } + return nil +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + if f == nil || f.impl == nil { + return nil + } + curr_off := seek(f, 0, .Current) or_return + defer seek(f, curr_off, .Start) + seek(f, size, .Start) or_return + handle := _handle(f) + if !win32.SetEndOfFile(handle) { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return + err, err1: Error + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } + if err == nil { + return nil + } + if !win32.RemoveDirectoryW(p) { + err1 = _get_platform_error() + } + if err1 == nil { + return nil + } + + if err != err1 { + a := win32.GetFileAttributesW(p) + if a == win32.INVALID_FILE_ATTRIBUTES { + err = _get_platform_error() + } else { + if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + err = err1 + } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { + if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { + err = nil + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } + } + } + } + } + + return err +} + +_rename :: proc(old_path, new_path: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + from := _fix_long_path(old_path, temp_allocator) or_return + to := _fix_long_path(new_path, temp_allocator) or_return + if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + return nil + } + return _get_platform_error() + +} + +_link :: proc(old_name, new_name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + o := _fix_long_path(old_name, temp_allocator) or_return + n := _fix_long_path(new_name, temp_allocator) or_return + if win32.CreateHardLinkW(n, o, nil) { + return nil + } + return _get_platform_error() +} + +_symlink :: proc(old_name, new_name: string) -> Error { + return .Unsupported +} + +_open_sym_link :: proc(p: cstring16) -> (handle: win32.HANDLE, err: Error) { + attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) + attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT + handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) + if handle == win32.INVALID_HANDLE { + return nil, _get_platform_error() + } + return + +} + +_normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: string, err: Error) { + has_prefix :: proc(p: []u16, str: string) -> bool { + if len(p) < len(str) { + return false + } + // assume ascii + for i in 0.. bool { + return has_prefix(p, `\??\`) + } + + if !has_unc_prefix(p) { + return win32_utf16_to_utf8(p, allocator) + } + + ws := p[4:] + switch { + case len(ws) >= 2 && ws[1] == ':': + return win32_utf16_to_utf8(ws, allocator) + case has_prefix(ws, `UNC\`): + ws[3] = '\\' // override data in buffer + return win32_utf16_to_utf8(ws[3:], allocator) + } + + + handle := _open_sym_link(cstring16(raw_data(p))) or_return + defer win32.CloseHandle(handle) + + n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := make([]u16, n+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(handle, cstring16(raw_data(buf)), u32(len(buf)), win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + + ws = buf[:n] + if has_unc_prefix(ws) { + ws = ws[4:] + if len(ws) > 3 && has_prefix(ws, `UNC`) { + ws[2] = '\\' + return win32_utf16_to_utf8(ws[2:], allocator) + } + return win32_utf16_to_utf8(ws, allocator) + } + return "", .Invalid_Path +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024 + + @thread_local + rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + p := _fix_long_path(name, temp_allocator) or_return + handle := _open_sym_link(p) or_return + defer win32.CloseHandle(handle) + + bytes_returned: u32 + if !win32.DeviceIoControl(handle, win32.FSCTL_GET_REPARSE_POINT, nil, 0, &rdb_buf[0], len(rdb_buf)-1, &bytes_returned, nil) { + err = _get_platform_error() + return + } + mem.zero_slice(rdb_buf[:min(bytes_returned+1, len(rdb_buf))]) + + + rdb := (^win32.REPARSE_DATA_BUFFER)(&rdb_buf[0]) + switch rdb.ReparseTag { + case win32.IO_REPARSE_TAG_SYMLINK: + rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest) + pb := ([^]u16)(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { + return win32_utf16_to_utf8(p, allocator) + } + return _normalize_link_path(p, allocator) + + case win32.IO_REPARSE_TAG_MOUNT_POINT: + rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest) + pb := ([^]u16)(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + return _normalize_link_path(p, allocator) + } + // Path wasn't a symlink/junction but another reparse point kind + return "", nil +} + + +_fchdir :: proc(f: ^File) -> Error { + if f == nil || f.impl == nil { + return nil + } + impl := (^File_Impl)(f.impl) + if !win32.SetCurrentDirectoryW(impl.wname) { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: Permissions) -> Error { + if f == nil || f.impl == nil { + return nil + } + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + attrs := d.dwFileAttributes + if .Write_User in mode { + attrs &~= win32.FILE_ATTRIBUTE_READONLY + } else { + attrs |= win32.FILE_ATTRIBUTE_READONLY + } + + info: win32.FILE_BASIC_INFO + info.FileAttributes = attrs + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + return _get_platform_error() + } + return nil +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chdir :: proc(name: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return + if !win32.SetCurrentDirectoryW(p) { + return _get_platform_error() + } + return nil +} + +_chmod :: proc(name: string, mode: Permissions) -> Error { + f := open(name, {.Write}) or_return + defer close(f) + return _fchmod(f, mode) +} + +_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 { + f := open(name, {.Write}) or_return + defer close(f) + return _fchtimes(f, atime, mtime) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + if f == nil || f.impl == nil { + return nil + } + + atime, mtime := atime, mtime + if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { + atime = mtime + } + + info: win32.FILE_BASIC_INFO + info.LastAccessTime = time_as_filetime(atime) + info.LastWriteTime = time_as_filetime(mtime) + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) { + return _get_platform_error() + } + return nil +} + +_exists :: proc(path: string) -> bool { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wpath, _ := _fix_long_path(path, temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + return attribs != win32.INVALID_FILE_ATTRIBUTES +} + +@(private="package") +_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) + switch mode { + case .Read: + n, err = _read(f, p) + return + case .Read_At: + n, err = _read_at(f, p, offset) + return + case .Write: + n, err = _write(f, p) + return + case .Write_At: + n, err = _write_at(f, p, offset) + return + case .Seek: + n, err = _seek(f, offset, whence) + return + case .Size: + n, err = _file_size(f) + return + case .Flush: + err = _flush(f) + return + case .Close, .Destroy: + err = _close(f) + return + 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 + } + return 0, .Unsupported +} + + + + +@(private="package", require_results) +win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: cstring16, err: runtime.Allocator_Error) { + ws = cstring16(raw_data(win32_utf8_to_utf16(s, allocator) or_return)) + return +} + +@(private="package", require_results) +win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) { + if len(s) < 1 { + return + } + + b := transmute([]byte)s + cstr := raw_data(b) + n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) + if n == 0 { + return nil, nil + } + + text := make([]u16, n+1, allocator) or_return + + n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n) + if n1 == 0 { + delete(text, allocator) + return + } + + text[n] = 0 + for n >= 1 && text[n-1] == 0 { + n -= 1 + } + ws = text[:n] + return +} + +@(private="package", require_results) +win32_wstring_to_utf8 :: proc(s: cstring16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if s == nil || s == "" { + return "", nil + } + return win32_utf16_to_utf8(string16(s), allocator) +} + +@(private="package") +win32_utf16_to_utf8 :: proc{ + win32_utf16_string16_to_utf8, + win32_utf16_u16_to_utf8, +} + +@(private="package", require_results) +win32_utf16_string16_to_utf8 :: proc(s: string16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return + } + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0.. (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return + } + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0.. runtime.Allocator { + return runtime.Allocator{ + procedure = heap_allocator_proc, + data = nil, + } +} + + +@(require_results) +heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { + return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) +} diff --git a/core/os/heap_js.odin b/core/os/heap_js.odin new file mode 100644 index 000000000..15990b517 --- /dev/null +++ b/core/os/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/heap_linux.odin b/core/os/heap_linux.odin new file mode 100644 index 000000000..1d1f12726 --- /dev/null +++ b/core/os/heap_linux.odin @@ -0,0 +1,6 @@ +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/heap_posix.odin b/core/os/heap_posix.odin new file mode 100644 index 000000000..1b52aed75 --- /dev/null +++ b/core/os/heap_posix.odin @@ -0,0 +1,7 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/heap_wasi.odin b/core/os/heap_wasi.odin new file mode 100644 index 000000000..7da3c4845 --- /dev/null +++ b/core/os/heap_wasi.odin @@ -0,0 +1,6 @@ +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/heap_windows.odin b/core/os/heap_windows.odin new file mode 100644 index 000000000..7fd4529a0 --- /dev/null +++ b/core/os/heap_windows.odin @@ -0,0 +1,106 @@ +#+private +package os2 + +import "core:mem" +import win32 "core:sys/windows" + +heap_alloc :: proc(size: int, zero_memory: bool) -> rawptr { + return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) +} + +heap_resize :: proc(ptr: rawptr, new_size: int, zero_memory: bool) -> rawptr { + if new_size == 0 { + heap_free(ptr) + return nil + } + if ptr == nil { + return heap_alloc(new_size, zero_memory) + } + + return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size)) +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return + } + win32.HeapFree(win32.GetProcessHeap(), 0, ptr) +} + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr), zero_memory) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space || allocated_mem == nil { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, mem.Allocator_Error) { + if p == nil { + return nil, nil + } + return aligned_alloc(new_size, new_alignment, true, p) + } + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return aligned_alloc(size, alignment, mode == .Alloc) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize, .Resize_Non_Zeroed: + if old_memory == nil { + return aligned_alloc(size, alignment, true) + } + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} diff --git a/core/os/internal_util.odin b/core/os/internal_util.odin new file mode 100644 index 000000000..9616af8b0 --- /dev/null +++ b/core/os/internal_util.odin @@ -0,0 +1,94 @@ +#+private +package os2 + +import "base:intrinsics" +import "base:runtime" +import "core:math/rand" + + +// Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix +// parts which are split by the last "*" +@(require_results) +_prefix_and_suffix :: proc(pattern: string) -> (prefix, suffix: string, err: Error) { + for i in 0..= 0; i -= 1 { + if pattern[i] == '*' { + prefix, suffix = pattern[:i], pattern[i+1:] + break + } + } + return +} + +@(require_results) +clone_string :: proc(s: string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + buf := make([]byte, len(s), allocator) or_return + copy(buf, s) + return string(buf), nil +} + + +@(require_results) +clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstring, err: runtime.Allocator_Error) { + res = "" // do not use a `nil` cstring + buf := make([]byte, len(s)+1, allocator) or_return + copy(buf, s) + buf[len(s)] = 0 + return cstring(&buf[0]), nil +} + +@(require_results) +string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { + s := string(b) + i := 0 + for ; i < len(s); i += 1 { + if s[i] == 0 { + break + } + } + return s[:i] +} + +@(require_results) +concatenate_strings_from_buffer :: proc(buf: []byte, strings: ..string) -> string { + n := 0 + for s in strings { + (n < len(buf)) or_break + n += copy(buf[n:], s) + } + n = min(len(buf), n) + return string(buf[:n]) +} + +@(require_results) +concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + n := 0 + for s in strings { + n += len(s) + } + buf := make([]byte, n, allocator) or_return + n = 0 + for s in strings { + n += copy(buf[n:], s) + } + return string(buf), nil +} + +@(require_results) +random_string :: proc(buf: []byte) -> string { + for i := 0; i < len(buf); i += 16 { + n := rand.uint64() + end := min(i + 16, len(buf)) + for j := i; j < end; j += 1 { + buf[j] = '0' + u8(n) % 10 + n >>= 4 + } + } + return string(buf) +} diff --git a/core/os/old/dir_unix.odin b/core/os/old/dir_unix.odin new file mode 100644 index 000000000..c3dd844ef --- /dev/null +++ b/core/os/old/dir_unix.odin @@ -0,0 +1,65 @@ +#+build darwin, linux, netbsd, freebsd, openbsd, haiku +package os + +import "core:strings" + +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + context.allocator = allocator + + dupfd := _dup(fd) or_return + dirp := _fdopendir(dupfd) or_return + defer _closedir(dirp) + + dirpath := absolute_path_from_handle(dupfd) or_return + defer delete(dirpath) + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + dfi := make([dynamic]File_Info, 0, size, allocator) or_return + defer if err != nil { + for fi_ in dfi { + file_info_delete(fi_, allocator) + } + delete(dfi) + } + + for { + entry: Dirent + end_of_stream: bool + entry, err, end_of_stream = _readdir(dirp) + if err != nil { + return + } else if end_of_stream { + break + } + + fi_: File_Info + filename := string(cstring(&entry.name[0])) + + if filename == "." || filename == ".." { + continue + } + + fullpath := strings.join({ dirpath, filename }, "/", allocator) + + s: OS_Stat + s, err = _lstat(fullpath) + if err != nil { + delete(fullpath, allocator) + return + } + _fill_file_info_from_stat(&fi_, s) + fi_.fullpath = fullpath + fi_.name = path_base(fi_.fullpath) + + append(&dfi, fi_) + } + + return dfi[:], nil +} 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/old/os.odin b/core/os/old/os.odin new file mode 100644 index 000000000..da7b0c151 --- /dev/null +++ b/core/os/old/os.odin @@ -0,0 +1,266 @@ +// Cross-platform `OS` interactions like file `I/O`. +package os + +import "base:intrinsics" +import "base:runtime" +import "core:io" +import "core:strconv" +import "core:strings" +import "core:unicode/utf8" + + +OS :: ODIN_OS +ARCH :: ODIN_ARCH +ENDIAN :: ODIN_ENDIAN + +SEEK_SET :: 0 +SEEK_CUR :: 1 +SEEK_END :: 2 + +write_string :: proc(fd: Handle, str: string) -> (int, Error) { + return write(fd, transmute([]byte)str) +} + +write_byte :: proc(fd: Handle, b: byte) -> (int, Error) { + return write(fd, []byte{b}) +} + +write_rune :: proc(fd: Handle, r: rune) -> (int, Error) { + if r < utf8.RUNE_SELF { + return write_byte(fd, byte(r)) + } + + b, n := utf8.encode_rune(r) + return write(fd, b[:n]) +} + +write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) { + wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { + n^ += m + if merr != nil { + err^ = merr + return true + } + return false + } + + if wrap(write_byte(f, '\''), &n, &err) { return } + + switch r { + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } + case: + if r < 32 { + if wrap(write_string(f, "\\x"), &n, &err) { return } + b: [2]byte + s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) + switch len(s) { + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } + } + } else { + if wrap(write_rune(f, r), &n, &err) { return } + } + } + _ = wrap(write_byte(f, '\''), &n, &err) + return +} + +read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, io.Error.Short_Buffer + } + nn := max(int) + for nn > 0 && n < min && err == nil { + nn, err = read(fd, buf[n:]) + n += nn + } + if n >= min { + err = nil + } + return +} + +read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) { + return read_at_least(fd, buf, len(buf)) +} + + +@(require_results) +file_size_from_path :: proc(path: string) -> i64 { + fd, err := open(path, O_RDONLY, 0) + if err != nil { + return -1 + } + defer close(fd) + + length: i64 + if length, err = file_size(fd); err != nil { + return -1 + } + return length +} + +@(require_results) +read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { + err: Error + data, err = read_entire_file_from_filename_or_err(name, allocator, loc) + success = err == nil + return +} + +@(require_results) +read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { + err: Error + data, err = read_entire_file_from_handle_or_err(fd, allocator, loc) + success = err == nil + return +} + +read_entire_file :: proc { + read_entire_file_from_filename, + read_entire_file_from_handle, +} + +@(require_results) +read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + fd := open(name, O_RDONLY, 0) or_return + defer close(fd) + + return read_entire_file_from_handle_or_err(fd, allocator, loc) +} + +@(require_results) +read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + length := file_size(fd) or_return + if length <= 0 { + return nil, nil + } + + data = make([]byte, int(length), allocator, loc) or_return + if data == nil { + return nil, nil + } + defer if err != nil { + delete(data, allocator) + } + + bytes_read := read_full(fd, data) or_return + data = data[:bytes_read] + return +} + +read_entire_file_or_err :: proc { + read_entire_file_from_filename_or_err, + read_entire_file_from_handle_or_err, +} + + +write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) { + return write_entire_file_or_err(name, data, truncate) == nil +} + +@(require_results) +write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error { + flags: int = O_WRONLY|O_CREATE + if truncate { + flags |= O_TRUNC + } + + mode: int = 0 + when OS == .Linux || OS == .Darwin { + // NOTE(justasd): 644 (owner read, write; group read; others read) + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + } + + fd := open(name, flags, mode) or_return + defer close(fd) + + for n := 0; n < len(data); { + n += write(fd, data[n:]) or_return + } + return nil +} + +write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { + return write(fd, ([^]byte)(data)[:len]) +} + +read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { + return read(fd, ([^]byte)(data)[:len]) +} + +heap_allocator_proc :: runtime.heap_allocator_proc +heap_allocator :: runtime.heap_allocator + +heap_alloc :: runtime.heap_alloc +heap_resize :: runtime.heap_resize +heap_free :: runtime.heap_free + +@(require_results) +processor_core_count :: proc() -> int { + return _processor_core_count() +} + +// Always allocates for consistency. +replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) { + path := path + + sb: strings.Builder + strings.builder_init_none(&sb, allocator) + for len(path) > 0 { + switch path[0] { + case '%': // Windows + when ODIN_OS == .Windows { + for r, i in path[1:] { + if r == '%' { + env_key := path[1:i+1] + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[i+1:] // % is part of key, so skip 1 character extra + } + } + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case '$': // Posix + when ODIN_OS != .Windows { + env_key := "" + dollar_loop: for r, i in path[1:] { + switch r { + case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident + case: + env_key = path[1:i+1] + break dollar_loop + } + } + if len(env_key) > 0 { + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[len(env_key):] + } + + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case: + strings.write_rune(&sb, rune(path[0])) + } + + path = path[1:] + } + return strings.to_string(sb) +} \ No newline at end of file diff --git a/core/os/old/os_darwin.odin b/core/os/old/os_darwin.odin new file mode 100644 index 000000000..92a636255 --- /dev/null +++ b/core/os/old/os_darwin.odin @@ -0,0 +1,1348 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:System" +foreign import pthread "system:System" + +import "base:runtime" +import "core:strings" +import "core:c" + +Handle :: distinct i32 +File_Time :: distinct u64 + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + ESRCH = 3, /* No such process */ + EINTR = 4, /* Interrupted system call */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device / Resource busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + ENOTSUP = 45, /* Operation not supported */ + EOPNOTSUPP = ENOTSUP, + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* Intelligent device errors */ + EPWROFF = 82, /* Device power is off */ + EDEVERR = 83, /* Device error, e.g. paper out */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Program loading errors */ + EBADEXEC = 85, /* Bad executable */ + EBADARCH = 86, /* Bad CPU type in executable */ + ESHLIBVERS = 87, /* Shared library version mismatch */ + EBADMACHO = 88, /* Malformed Macho file */ + + ECANCELED = 89, /* Operation canceled */ + + EIDRM = 90, /* Identifier removed */ + ENOMSG = 91, /* No message of desired type */ + EILSEQ = 92, /* Illegal byte sequence */ + ENOATTR = 93, /* Attribute not found */ + + EBADMSG = 94, /* Bad message */ + EMULTIHOP = 95, /* Reserved */ + ENODATA = 96, /* No message available on STREAM */ + ENOLINK = 97, /* Reserved */ + ENOSR = 98, /* No STREAM resources */ + ENOSTR = 99, /* Not a STREAM */ + EPROTO = 100, /* Protocol error */ + ETIME = 101, /* STREAM ioctl timeout */ + + ENOPOLICY = 103, /* No such policy registered */ + + ENOTRECOVERABLE = 104, /* State not recoverable */ + EOWNERDEAD = 105, /* Previous owner died */ + + EQFULL = 106, /* Interface output queue is full */ + ELAST = 106, /* Must be equal largest errno */ +} + +EPERM :: _Platform_Error.EPERM +ENOENT :: _Platform_Error.ENOENT +ESRCH :: _Platform_Error.ESRCH +EINTR :: _Platform_Error.EINTR +EIO :: _Platform_Error.EIO +ENXIO :: _Platform_Error.ENXIO +E2BIG :: _Platform_Error.E2BIG +ENOEXEC :: _Platform_Error.ENOEXEC +EBADF :: _Platform_Error.EBADF +ECHILD :: _Platform_Error.ECHILD +EDEADLK :: _Platform_Error.EDEADLK +ENOMEM :: _Platform_Error.ENOMEM +EACCES :: _Platform_Error.EACCES +EFAULT :: _Platform_Error.EFAULT +ENOTBLK :: _Platform_Error.ENOTBLK +EBUSY :: _Platform_Error.EBUSY +EEXIST :: _Platform_Error.EEXIST +EXDEV :: _Platform_Error.EXDEV +ENODEV :: _Platform_Error.ENODEV +ENOTDIR :: _Platform_Error.ENOTDIR +EISDIR :: _Platform_Error.EISDIR +EINVAL :: _Platform_Error.EINVAL +ENFILE :: _Platform_Error.ENFILE +EMFILE :: _Platform_Error.EMFILE +ENOTTY :: _Platform_Error.ENOTTY +ETXTBSY :: _Platform_Error.ETXTBSY +EFBIG :: _Platform_Error.EFBIG +ENOSPC :: _Platform_Error.ENOSPC +ESPIPE :: _Platform_Error.ESPIPE +EROFS :: _Platform_Error.EROFS +EMLINK :: _Platform_Error.EMLINK +EPIPE :: _Platform_Error.EPIPE + +/* math software */ +EDOM :: _Platform_Error.EDOM +ERANGE :: _Platform_Error.ERANGE + +/* non-blocking and interrupt i/o */ +EAGAIN :: _Platform_Error.EAGAIN +EWOULDBLOCK :: _Platform_Error.EWOULDBLOCK +EINPROGRESS :: _Platform_Error.EINPROGRESS +EALREADY :: _Platform_Error.EALREADY + +/* ipc/network software -- argument errors */ +ENOTSOCK :: _Platform_Error.ENOTSOCK +EDESTADDRREQ :: _Platform_Error.EDESTADDRREQ +EMSGSIZE :: _Platform_Error.EMSGSIZE +EPROTOTYPE :: _Platform_Error.EPROTOTYPE +ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT +ENOTSUP :: _Platform_Error.ENOTSUP +EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT +EADDRINUSE :: _Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: _Platform_Error.EADDRNOTAVAIL + +/* ipc/network software -- operational errors */ +ENETDOWN :: _Platform_Error.ENETDOWN +ENETUNREACH :: _Platform_Error.ENETUNREACH +ENETRESET :: _Platform_Error.ENETRESET +ECONNABORTED :: _Platform_Error.ECONNABORTED +ECONNRESET :: _Platform_Error.ECONNRESET +ENOBUFS :: _Platform_Error.ENOBUFS +EISCONN :: _Platform_Error.EISCONN +ENOTCONN :: _Platform_Error.ENOTCONN +ESHUTDOWN :: _Platform_Error.ESHUTDOWN +ETOOMANYREFS :: _Platform_Error.ETOOMANYREFS +ETIMEDOUT :: _Platform_Error.ETIMEDOUT +ECONNREFUSED :: _Platform_Error.ECONNREFUSED + +ELOOP :: _Platform_Error.ELOOP +ENAMETOOLONG :: _Platform_Error.ENAMETOOLONG + +/* should be rearranged */ +EHOSTDOWN :: _Platform_Error.EHOSTDOWN +EHOSTUNREACH :: _Platform_Error.EHOSTUNREACH +ENOTEMPTY :: _Platform_Error.ENOTEMPTY + +/* quotas & mush */ +EPROCLIM :: _Platform_Error.EPROCLIM +EUSERS :: _Platform_Error.EUSERS +EDQUOT :: _Platform_Error.EDQUOT + +/* Network File System */ +ESTALE :: _Platform_Error.ESTALE +EREMOTE :: _Platform_Error.EREMOTE +EBADRPC :: _Platform_Error.EBADRPC +ERPCMISMATCH :: _Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: _Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: _Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: _Platform_Error.EPROCUNAVAIL + +ENOLCK :: _Platform_Error.ENOLCK +ENOSYS :: _Platform_Error.ENOSYS + +EFTYPE :: _Platform_Error.EFTYPE +EAUTH :: _Platform_Error.EAUTH +ENEEDAUTH :: _Platform_Error.ENEEDAUTH + +/* Intelligent device errors */ +EPWROFF :: _Platform_Error.EPWROFF +EDEVERR :: _Platform_Error.EDEVERR +EOVERFLOW :: _Platform_Error.EOVERFLOW + +/* Program loading errors */ +EBADEXEC :: _Platform_Error.EBADEXEC +EBADARCH :: _Platform_Error.EBADARCH +ESHLIBVERS :: _Platform_Error.ESHLIBVERS +EBADMACHO :: _Platform_Error.EBADMACHO + +ECANCELED :: _Platform_Error.ECANCELED + +EIDRM :: _Platform_Error.EIDRM +ENOMSG :: _Platform_Error.ENOMSG +EILSEQ :: _Platform_Error.EILSEQ +ENOATTR :: _Platform_Error.ENOATTR + +EBADMSG :: _Platform_Error.EBADMSG +EMULTIHOP :: _Platform_Error.EMULTIHOP +ENODATA :: _Platform_Error.ENODATA +ENOLINK :: _Platform_Error.ENOLINK +ENOSR :: _Platform_Error.ENOSR +ENOSTR :: _Platform_Error.ENOSTR +EPROTO :: _Platform_Error.EPROTO +ETIME :: _Platform_Error.ETIME + +ENOPOLICY :: _Platform_Error.ENOPOLICY + +ENOTRECOVERABLE :: _Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: _Platform_Error.EOWNERDEAD + +EQFULL :: _Platform_Error.EQFULL +ELAST :: _Platform_Error.ELAST + + +O_RDONLY :: 0x0000 +O_WRONLY :: 0x0001 +O_RDWR :: 0x0002 +O_CREATE :: 0x0200 +O_EXCL :: 0x0800 +O_NOCTTY :: 0 +O_TRUNC :: 0x0400 +O_NONBLOCK :: 0x0004 +O_APPEND :: 0x0008 +O_SYNC :: 0x0080 +O_ASYNC :: 0x0040 +O_CLOEXEC :: 0x1000000 + +SEEK_DATA :: 3 +SEEK_HOLE :: 4 +SEEK_MAX :: SEEK_HOLE + + + +// NOTE(zangent): These are OS specific! +// Do not mix these up! +RTLD_LAZY :: 0x1 +RTLD_NOW :: 0x2 +RTLD_LOCAL :: 0x4 +RTLD_GLOBAL :: 0x8 +RTLD_NODELETE :: 0x80 +RTLD_NOLOAD :: 0x10 +RTLD_FIRST :: 0x100 + +SOL_SOCKET :: 0xFFFF + +SOCK_STREAM :: 1 +SOCK_DGRAM :: 2 +SOCK_RAW :: 3 +SOCK_RDM :: 4 +SOCK_SEQPACKET :: 5 + +SO_DEBUG :: 0x0001 +SO_ACCEPTCONN :: 0x0002 +SO_REUSEADDR :: 0x0004 +SO_KEEPALIVE :: 0x0008 +SO_DONTROUTE :: 0x0010 +SO_BROADCAST :: 0x0020 +SO_USELOOPBACK :: 0x0040 +SO_LINGER :: 0x0080 +SO_OOBINLINE :: 0x0100 +SO_REUSEPORT :: 0x0200 +SO_TIMESTAMP :: 0x0400 + +SO_DONTTRUNC :: 0x2000 +SO_WANTMORE :: 0x4000 +SO_WANTOOBFLAG :: 0x8000 +SO_SNDBUF :: 0x1001 +SO_RCVBUF :: 0x1002 +SO_SNDLOWAT :: 0x1003 +SO_RCVLOWAT :: 0x1004 +SO_SNDTIMEO :: 0x1005 +SO_RCVTIMEO :: 0x1006 +SO_ERROR :: 0x1007 +SO_TYPE :: 0x1008 +SO_PRIVSTATE :: 0x1009 +SO_NREAD :: 0x1020 +SO_NKE :: 0x1021 + +AF_UNSPEC :: 0 +AF_LOCAL :: 1 +AF_UNIX :: AF_LOCAL +AF_INET :: 2 +AF_IMPLINK :: 3 +AF_PUP :: 4 +AF_CHAOS :: 5 +AF_NS :: 6 +AF_ISO :: 7 +AF_OSI :: AF_ISO +AF_ECMA :: 8 +AF_DATAKIT :: 9 +AF_CCITT :: 10 +AF_SNA :: 11 +AF_DECnet :: 12 +AF_DLI :: 13 +AF_LAT :: 14 +AF_HYLINK :: 15 +AF_APPLETALK :: 16 +AF_ROUTE :: 17 +AF_LINK :: 18 +pseudo_AF_XTP :: 19 +AF_COIP :: 20 +AF_CNT :: 21 +pseudo_AF_RTIP :: 22 +AF_IPX :: 23 +AF_SIP :: 24 +pseudo_AF_PIP :: 25 +pseudo_AF_BLUE :: 26 +AF_NDRV :: 27 +AF_ISDN :: 28 +AF_E164 :: AF_ISDN +pseudo_AF_KEY :: 29 +AF_INET6 :: 30 +AF_NATM :: 31 +AF_SYSTEM :: 32 +AF_NETBIOS :: 33 +AF_PPP :: 34 + +TCP_NODELAY :: 0x01 +TCP_MAXSEG :: 0x02 +TCP_NOPUSH :: 0x04 +TCP_NOOPT :: 0x08 + +IPPROTO_ICMP :: 1 +IPPROTO_TCP :: 6 +IPPROTO_UDP :: 17 + +SHUT_RD :: 0 +SHUT_WR :: 1 +SHUT_RDWR :: 2 + +F_GETFL: int : 3 /* Get file flags */ +F_SETFL: int : 4 /* Set file flags */ + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + +OS_Stat :: struct { + device_id: i32, // ID of device containing file + mode: u16, // Mode of the file + nlink: u16, // Number of hard links + serial: u64, // File serial number + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + rdev: i32, // Device ID, if device + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + created: Unix_File_Time, // Time of creation + + size: i64, // Size of the file, in bytes + blocks: i64, // Number of blocks allocated for the file + block_size: i32, // Optimal blocksize for I/O + flags: u32, // User-defined flags for the file + gen_num: u32, // File generation number ..? + _spare: i32, // RESERVED + _reserve1, + _reserve2: i64, // RESERVED +} + +DARWIN_MAXPATHLEN :: 1024 +Dirent :: struct { + ino: u64, + off: u64, + reclen: u16, + namlen: u16, + type: u8, + name: [DARWIN_MAXPATHLEN]byte, +} + +Dir :: distinct rawptr // DIR* + +ADDRESS_FAMILY :: c.char +SOCKADDR :: struct #packed { + len: c.char, + family: ADDRESS_FAMILY, + sa_data: [14]c.char, +} + +SOCKADDR_STORAGE_LH :: struct #packed { + len: c.char, + family: ADDRESS_FAMILY, + __ss_pad1: [6]c.char, + __ss_align: i64, + __ss_pad2: [112]c.char, +} + +sockaddr_in :: struct #packed { + sin_len: c.char, + sin_family: ADDRESS_FAMILY, + sin_port: u16be, + sin_addr: in_addr, + sin_zero: [8]c.char, +} + +sockaddr_in6 :: struct #packed { + sin6_len: c.char, + sin6_family: ADDRESS_FAMILY, + sin6_port: u16be, + sin6_flowinfo: c.uint, + sin6_addr: in6_addr, + sin6_scope_id: c.uint, +} + +in_addr :: struct #packed { + s_addr: u32, +} + +in6_addr :: struct #packed { + s6_addr: [16]u8, +} + +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1025-L1027 +// Prevent the raising of SIGPIPE on writing to a closed network socket. +MSG_NOSIGNAL :: 0x80000 + +SIOCGIFFLAG :: enum c.int { + UP = 0, /* Interface is up. */ + BROADCAST = 1, /* Broadcast address valid. */ + DEBUG = 2, /* Turn on debugging. */ + LOOPBACK = 3, /* Is a loopback net. */ + POINT_TO_POINT = 4, /* Interface is point-to-point link. */ + NO_TRAILERS = 5, /* Avoid use of trailers. */ + RUNNING = 6, /* Resources allocated. */ + NOARP = 7, /* No address resolution protocol. */ + PROMISC = 8, /* Receive all packets. */ + ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */ +} +SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] + +ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: SIOCGIFFLAGS, + address: ^SOCKADDR, + netmask: ^SOCKADDR, + broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address + data: rawptr, // Address-specific data. +} + +Timeval :: struct { + seconds: i64, + microseconds: int, +} + +Linger :: struct { + onoff: int, + linger: int, +} + +Socket :: distinct int +socklen_t :: c.int + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + +@(require_results) S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } + +R_OK :: 4 // Test for read permission +W_OK :: 2 // Test for write permission +X_OK :: 1 // Test for execute permission +F_OK :: 0 // Test for file existance + +F_GETPATH :: 50 // return the full path of the fd + +foreign libc { + @(link_name="__error") __error :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- + @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- + @(link_name="pread") _unix_pread :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- + @(link_name="pwrite") _unix_pwrite :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- + @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: c.int) -> int --- + @(link_name="gettid") _unix_gettid :: proc() -> u64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 --- + @(link_name="stat64") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(link_name="lstat64") _unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(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="dup") _unix_dup :: proc(handle: Handle) -> Handle --- + + @(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- + @(link_name="readdir_r$INODE64") _unix_readdir_r_amd64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="fdopendir") _unix_fdopendir_arm64 :: proc(fd: Handle) -> Dir --- + @(link_name="readdir_r") _unix_readdir_r_arm64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + + @(link_name="__fcntl") _unix__fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + + @(link_name="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int --- + @(link_name="remove") _unix_remove :: proc(path: cstring) -> c.int --- + + @(link_name="fchmod") _unix_fchmod :: proc(fd: Handle, mode: u16) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="unsetenv") _unix_unsetenv :: proc(cstring) -> c.int --- + @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- + + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(buf: cstring, mode: u16) -> c.int --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + + @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="socket") _unix_socket :: proc(domain: c.int, type: c.int, protocol: c.int) -> c.int --- + @(link_name="listen") _unix_listen :: proc(socket: c.int, backlog: c.int) -> c.int --- + @(link_name="accept") _unix_accept :: proc(socket: c.int, addr: rawptr, addr_len: rawptr) -> c.int --- + @(link_name="connect") _unix_connect :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="bind") _unix_bind :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="setsockopt") _unix_setsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: socklen_t) -> c.int --- + @(link_name="getsockopt") _unix_getsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: ^socklen_t) -> c.int --- + @(link_name="recvfrom") _unix_recvfrom :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- + @(link_name="recv") _unix_recv :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="sendto") _unix_sendto :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- + @(link_name="send") _unix_send :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="shutdown") _unix_shutdown :: proc(socket: c.int, how: c.int) -> c.int --- + + @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- + @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} + +when ODIN_ARCH != .arm64 { + _unix_fdopendir :: proc {_unix_fdopendir_amd64} + _unix_readdir_r :: proc {_unix_readdir_r_amd64} +} else { + _unix_fdopendir :: proc {_unix_fdopendir_arm64} + _unix_readdir_r :: proc {_unix_readdir_r_arm64} +} + +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) +} + +@(require_results) +get_last_error_string :: proc() -> string { + return string(_darwin_string_error(__error()^)) +} + + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (handle: Handle, err: Error) { + isDir := is_dir_path(path) + flags := flags + if isDir { + /* + @INFO(Platin): To make it impossible to use the wrong flag for dir's + as you can't write to a dir only read which makes it fail to open + */ + flags = O_RDONLY + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle = _unix_open(cstr, i32(flags), u16(mode)) + if handle == INVALID_HANDLE { + err = get_last_error() + return + } + + return +} + +fchmod :: proc(fd: Handle, mode: u16) -> Error { + return cast(Platform_Error)_unix_fchmod(fd, mode) +} + +close :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_close(fd) +} + +// If you read or write more than `SSIZE_MAX` bytes, most darwin implementations will return `EINVAL` +// but it is really implementation defined. `SSIZE_MAX` is also implementation defined but usually +// the max of an i32 on Darwin. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + + bytes_written := _unix_write(fd, raw_data(data), to_write) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +read :: proc(fd: Handle, data: []u8) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(c.size_t(len(data)), MAX_RW) + + bytes_read := _unix_read(fd, raw_data(data), to_read) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(c.size_t(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + assert(fd != -1) + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + + final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) + if final_offset == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return final_offset, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + prev, _ := seek(fd, 0, SEEK_CUR) + size, err := seek(fd, 0, SEEK_END) + seek(fd, prev, SEEK_SET) + return i64(size), err +} + + + +// NOTE(bill): Uses startup to initialize it +stdin: Handle = 0 // get_std_handle(win32.STD_INPUT_HANDLE); +stdout: Handle = 1 // get_std_handle(win32.STD_OUTPUT_HANDLE); +stderr: Handle = 2 // get_std_handle(win32.STD_ERROR_HANDLE); + +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +@(require_results) +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} + +rename :: proc(old: string, new: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_cstr := strings.clone_to_cstring(old, context.temp_allocator) + new_cstr := strings.clone_to_cstring(new, context.temp_allocator) + return _unix_rename(old_cstr, new_cstr) != -1 +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_remove(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + s: OS_Stat + result := _unix_stat(cstr, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + s: OS_Stat + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + s: OS_Stat + result := _unix_fstat(fd, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + + if result == nil { + end_of_stream = true + return + } + end_of_stream = false + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = 256 + buf := make([]byte, bufsz) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? + bufsz *= 2 + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { + buf: [DARWIN_MAXPATHLEN]byte + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _unix_access(cstr, c.int(mask)) == 0 +} + +flush :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_fsync(fd) +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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 :: proc(key, value: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) + res := _unix_setenv(key_cstring, value_cstring, 1) + if res < 0 { + return get_last_error() + } + return nil +} + +unset_env :: proc(key: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_unsetenv(s) + if res < 0 { + return get_last_error() + } + return nil +} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. + buf := make([dynamic]u8, page_size) + for { + cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if get_last_error() != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +make_directory :: proc(path: string, mode: u16 = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return get_last_error() + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(i32(code)) +} + +@(require_results) +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) +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private, require_results) +_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 +} + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for _, i in res { + res[i] = string(runtime.args__[i]) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { + result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) + if result < 0 { + return 0, get_last_error() + } + return Socket(result), nil +} + +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { + result := _unix_connect(c.int(sd), addr, len) + if result < 0 { + return get_last_error() + } + return nil +} + +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Error) { + result := _unix_bind(c.int(sd), addr, len) + if result < 0 { + return get_last_error() + } + return nil +} + +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { + result := _unix_accept(c.int(sd), rawptr(addr), len) + if result < 0 { + return 0, get_last_error() + } + return Socket(result), nil +} + +listen :: proc(sd: Socket, backlog: int) -> (Error) { + result := _unix_listen(c.int(sd), c.int(backlog)) + if result < 0 { + return get_last_error() + } + return nil +} + +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { + result := _unix_setsockopt(c.int(sd), c.int(level), c.int(optname), optval, optlen) + if result < 0 { + return get_last_error() + } + return nil +} + +getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { + optlen := optlen + result := _unix_getsockopt(c.int(sd), c.int(level), c.int(optname), optval, &optlen) + if result < 0 { + return get_last_error() + } + return nil +} + +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { + result := _unix_recvfrom(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addr_size) + if result < 0 { + return 0, get_last_error() + } + return u32(result), nil +} + +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := _unix_recv(c.int(sd), raw_data(data), len(data), c.int(flags)) + if result < 0 { + return 0, get_last_error() + } + return u32(result), nil +} + +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { + result := _unix_sendto(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addrlen) + if result < 0 { + return 0, get_last_error() + } + return u32(result), nil +} + +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags)) + if result < 0 { + return 0, get_last_error() + } + return u32(result), nil +} + +shutdown :: proc(sd: Socket, how: int) -> (Error) { + result := _unix_shutdown(c.int(sd), c.int(how)) + if result < 0 { + return get_last_error() + } + return nil +} + +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { + result := _unix__fcntl(Handle(fd), c.int(cmd), uintptr(arg)) + if result < 0 { + return 0, get_last_error() + } + return int(result), nil +} diff --git a/core/os/old/os_essence.odin b/core/os/old/os_essence.odin new file mode 100644 index 000000000..75c4c1156 --- /dev/null +++ b/core/os/old/os_essence.odin @@ -0,0 +1,60 @@ +package os + +import "core:sys/es" + +Handle :: distinct int +_Platform_Error :: enum i32 {NONE} + +// ERROR_NONE :: Error(es.SUCCESS) + +O_RDONLY :: 0x1 +O_WRONLY :: 0x2 +O_CREATE :: 0x4 +O_TRUNC :: 0x8 + +stderr : Handle = 0 + +current_thread_id :: proc "contextless" () -> int { + return (int) (es.ThreadGetID(es.CURRENT_THREAD)) +} + +heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { + return es.HeapAllocate(size, zero_memory) +} + +heap_free :: proc(ptr: rawptr) { + es.HeapFree(ptr) +} + +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + return es.HeapReallocate(ptr, new_size, false) +} + +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + return (Handle) (0), (Error) (1) +} + +close :: proc(fd: Handle) -> Error { + return (Error) (1) +} + +file_size :: proc(fd: Handle) -> (i64, Error) { + return (i64) (0), (Error) (1) +} + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + return (int) (0), (Error) (1) +} + +write :: proc(fd: Handle, data: []u8) -> (int, Error) { + return (int) (0), (Error) (1) +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + return (i64) (0), (Error) (1) +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} \ No newline at end of file diff --git a/core/os/old/os_freebsd.odin b/core/os/old/os_freebsd.odin new file mode 100644 index 000000000..82b5a2f0f --- /dev/null +++ b/core/os/old/os_freebsd.odin @@ -0,0 +1,982 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +import "base:runtime" +import "core:strings" +import "core:c" +import "core:sys/freebsd" + +Handle :: distinct i32 +File_Time :: distinct u64 + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EBEADLK = 11, + ENOMEM = 12, + EACCESS = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, /* Result too large */ + EAGAIN = 35, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLING = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIDRM = 82, + ENOMSG = 83, + EOVERFLOW = 84, + ECANCELED = 85, + EILSEQ = 86, + ENOATTR = 87, + EDOOFUS = 88, + EBADMSG = 89, + EMULTIHOP = 90, + ENOLINK = 91, + EPROTO = 92, + ENOTCAPABLE = 93, + ECAPMODE = 94, + ENOTRECOVERABLE = 95, + EOWNERDEAD = 96, +} +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EBEADLK :: Platform_Error.EBEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCESS :: Platform_Error.EACCESS +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLING :: Platform_Error.ENAMETOOLING +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EILSEQ :: Platform_Error.EILSEQ +ENOATTR :: Platform_Error.ENOATTR +EDOOFUS :: Platform_Error.EDOOFUS +EBADMSG :: Platform_Error.EBADMSG +EMULTIHOP :: Platform_Error.EMULTIHOP +ENOLINK :: Platform_Error.ENOLINK +EPROTO :: Platform_Error.EPROTO +ENOTCAPABLE :: Platform_Error.ENOTCAPABLE +ECAPMODE :: Platform_Error.ECAPMODE +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_NONBLOCK :: 0x00004 +O_APPEND :: 0x00008 +O_ASYNC :: 0x00040 +O_SYNC :: 0x00080 +O_CREATE :: 0x00200 +O_TRUNC :: 0x00400 +O_EXCL :: 0x00800 +O_NOCTTY :: 0x08000 +O_CLOEXEC :: 0100000 + + +SEEK_DATA :: 3 +SEEK_HOLE :: 4 +SEEK_MAX :: SEEK_HOLE + +// NOTE: These are OS specific! +// Do not mix these up! +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +//RTLD_BINDING_MASK :: 0x3 // Called MODEMASK in dlfcn.h +RTLD_GLOBAL :: 0x100 +RTLD_LOCAL :: 0x000 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x01000 +RTLD_NOLOAD :: 0x02000 + +MAX_PATH :: 1024 + +KINFO_FILE_SIZE :: 1392 + +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +dev_t :: u64 +ino_t :: u64 +nlink_t :: u64 +off_t :: i64 +mode_t :: u16 +pid_t :: u32 +uid_t :: u32 +gid_t :: u32 +blkcnt_t :: i64 +blksize_t :: i32 +fflags_t :: u32 + +when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ { + time_t :: i64 +} else { + time_t :: i32 +} + + +OS_Stat :: struct { + device_id: dev_t, + serial: ino_t, + nlink: nlink_t, + mode: mode_t, + _padding0: i16, + uid: uid_t, + gid: gid_t, + _padding1: i32, + rdev: dev_t, + + last_access: Unix_File_Time, + modified: Unix_File_Time, + status_change: Unix_File_Time, + birthtime: Unix_File_Time, + + size: off_t, + blocks: blkcnt_t, + block_size: blksize_t, + + flags: fflags_t, + gen: u64, + lspare: [10]u64, +} + +KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [MAX_PATH]c.char, +} + +// since FreeBSD v12 +Dirent :: struct { + ino: ino_t, + off: off_t, + reclen: u16, + type: u8, + _pad0: u8, + namlen: u16, + _pad1: u16, + name: [256]byte, +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket +//S_ISVTX :: 0o001000 // Save swapped text even after use + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + + // Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + + // Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + + +@(require_results) S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +F_KINFO :: 22 + +foreign libc { + @(link_name="__error") __Error_location :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _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="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + + @(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int --- +} + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__Error_location()^) +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), u16(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) +} + +// If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) + + return bytes_read, cast(_Platform_Error)errno +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) + + return bytes_written, cast(_Platform_Error)errno +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case: + return 0, errno + } + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return +} + +rename :: proc(old_path, new_path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return get_last_error() + } + return nil +} + +remove_directory :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +@(require_results) +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +/* TODO(zangent): Implement these! +last_write_time :: proc(fd: Handle) -> File_Time {} +last_write_time_by_name :: proc(name: string) -> File_Time {} +*/ +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { + s, err := _fstat(fd) + if err != nil { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { + s, err := _stat(name) + if err != nil { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(private, require_results, no_sanitize_memory) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } + + return "", Error{} +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + kinfo: KInfo_File + kinfo.structsize = KINFO_FILE_SIZE + + res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo) + if res == -1 { + return "", get_last_error() + } + + path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) + return path, nil +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + #no_bounds_check for { + cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if get_last_error() != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return cast(int) pthread_getthreadid_np() +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private, require_results) +_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 +} + + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for _, i in res { + res[i] = string(runtime.args__[i]) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/old/os_freestanding.odin b/core/os/old/os_freestanding.odin new file mode 100644 index 000000000..c22a6d7d5 --- /dev/null +++ b/core/os/old/os_freestanding.odin @@ -0,0 +1,4 @@ +#+build freestanding +package os + +#panic("package os does not support a freestanding target") diff --git a/core/os/old/os_haiku.odin b/core/os/old/os_haiku.odin new file mode 100644 index 000000000..ad984e33c --- /dev/null +++ b/core/os/old/os_haiku.odin @@ -0,0 +1,544 @@ +package os + +foreign import lib "system:c" + +import "base:runtime" +import "core:c" +import "core:c/libc" +import "core:strings" +import "core:sys/haiku" +import "core:sys/posix" + +Handle :: i32 +Pid :: i32 +File_Time :: i64 +_Platform_Error :: haiku.Errno + +MAX_PATH :: haiku.PATH_MAX + +ENOSYS :: _Platform_Error(haiku.Errno.ENOSYS) + +INVALID_HANDLE :: ~Handle(0) + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +pid_t :: haiku.pid_t +off_t :: haiku.off_t +dev_t :: haiku.dev_t +ino_t :: haiku.ino_t +mode_t :: haiku.mode_t +nlink_t :: haiku.nlink_t +uid_t :: haiku.uid_t +gid_t :: haiku.gid_t +blksize_t :: haiku.blksize_t +blkcnt_t :: haiku.blkcnt_t +time_t :: haiku.time_t + + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +OS_Stat :: struct { + device_id: dev_t, // device ID that this file resides on + serial: ino_t, // this file's serial inode ID + mode: mode_t, // file mode (rwx for user, group, etc) + nlink: nlink_t, // number of hard links to this file + uid: uid_t, // user ID of the file's owner + gid: gid_t, // group ID of the file's group + size: off_t, // file size, in bytes + rdev: dev_t, // device type (not used) + block_size: blksize_t, // optimal blocksize for I/O + + last_access: Unix_File_Time, // time of last access + modified: Unix_File_Time, // time of last data modification + status_change: Unix_File_Time, // time of last file status change + birthtime: Unix_File_Time, // time of file creation + + type: u32, // attribute/index type + + blocks: blkcnt_t, // blocks allocated for file +} + +/* file access modes for open() */ +O_RDONLY :: 0x0000 /* read only */ +O_WRONLY :: 0x0001 /* write only */ +O_RDWR :: 0x0002 /* read and write */ +O_ACCMODE :: 0x0003 /* mask to get the access modes above */ +O_RWMASK :: O_ACCMODE + +/* flags for open() */ +O_EXCL :: 0x0100 /* exclusive creat */ +O_CREATE :: 0x0200 /* create and open file */ +O_TRUNC :: 0x0400 /* open with truncation */ +O_NOCTTY :: 0x1000 /* don't make tty the controlling tty */ +O_NOTRAVERSE :: 0x2000 /* do not traverse leaf link */ + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket +S_ISVTX :: 0o001000 // Save swapped text even after use + +// File mode + // Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + + // Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + + // Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISTXT :: 0o1000 // Sticky bit + +S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + +__error :: libc.errno +_unix_open :: posix.open + +foreign lib { + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +MAXNAMLEN :: haiku.NAME_MAX + +Dirent :: struct { + dev: dev_t, + pdef: dev_t, + ino: ino_t, + pino: ino_t, + reclen: u16, + name: [MAXNAMLEN + 1]byte, // name +} + +Dir :: distinct rawptr // DIR* + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) +} + +@(require_results) +fork :: proc() -> (Pid, Error) { + pid := _unix_fork() + if pid == -1 { + return Pid(-1), get_last_error() + } + return Pid(pid), nil +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := cast(Handle)_unix_open(cstr, transmute(posix.O_Flags)i32(flags), transmute(posix.mode_t)i32(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .BAD_VALUE: + return 0, .Invalid_Offset + } + return 0, errno + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + s, err := _fstat(fd) + if err != nil { + return -1, err + } + return s.size, nil +} + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + +@(private, require_results, no_sanitize_memory) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_stat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_fstat(fd, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + path_cstr := cstring(path_ptr) + return strings.clone(string(path_cstr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cstr, c.int(mask)) + if res == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} + + +@(private, require_results) +_processor_core_count :: proc() -> int { + info: haiku.system_info + haiku.get_system_info(&info) + return int(info.cpu_count) +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(i32(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return int(haiku.find_thread(nil)) +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} diff --git a/core/os/old/os_js.odin b/core/os/old/os_js.odin new file mode 100644 index 000000000..1870218d3 --- /dev/null +++ b/core/os/old/os_js.odin @@ -0,0 +1,275 @@ +#+build js +package os + +foreign import "odin_env" + +@(require_results) +is_path_separator :: proc(c: byte) -> bool { + return c == '/' || c == '\\' +} + +Handle :: distinct u32 + +stdout: Handle = 1 +stderr: Handle = 2 + +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { + unimplemented("core:os procedure not supported on JS target") +} + +close :: proc(fd: Handle) -> Error { + return nil +} + +flush :: proc(fd: Handle) -> (err: Error) { + return nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + foreign odin_env { + @(link_name="write") + _write :: proc "contextless" (fd: Handle, p: []byte) --- + } + _write(fd, data) + return len(data), nil +} + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + unimplemented("core:os procedure not supported on JS target") +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + unimplemented("core:os procedure not supported on JS target") +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + unimplemented("core:os procedure not supported on JS target") +} +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +exists :: proc(path: string) -> bool { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +is_file :: proc(path: string) -> bool { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +is_dir :: proc(path: string) -> bool { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + unimplemented("core:os procedure not supported on JS target") +} + +set_current_directory :: proc(path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + + +change_directory :: proc(path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +remove_directory :: proc(path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +link :: proc(old_name, new_name: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +unlink :: proc(path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + + +rename :: proc(old_path, new_path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +truncate :: proc(path: string, length: i64) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +remove :: proc(name: string) -> Error { + unimplemented("core:os procedure not supported on JS target") +} + + +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +File_Time :: distinct u64 + +_Platform_Error :: enum i32 { + NONE = 0, + FILE_NOT_FOUND = 2, + PATH_NOT_FOUND = 3, + ACCESS_DENIED = 5, + INVALID_HANDLE = 6, + NOT_ENOUGH_MEMORY = 8, + NO_MORE_FILES = 18, + HANDLE_EOF = 38, + NETNAME_DELETED = 64, + FILE_EXISTS = 80, + INVALID_PARAMETER = 87, + BROKEN_PIPE = 109, + BUFFER_OVERFLOW = 111, + INSUFFICIENT_BUFFER = 122, + MOD_NOT_FOUND = 126, + PROC_NOT_FOUND = 127, + DIR_NOT_EMPTY = 145, + ALREADY_EXISTS = 183, + ENVVAR_NOT_FOUND = 203, + MORE_DATA = 234, + OPERATION_ABORTED = 995, + IO_PENDING = 997, + NOT_FOUND = 1168, + PRIVILEGE_NOT_HELD = 1314, + WSAEACCES = 10013, + WSAECONNRESET = 10054, + + // Windows reserves errors >= 1<<29 for application use + FILE_IS_PIPE = 1<<29 + 0, + FILE_IS_NOT_DIR = 1<<29 + 1, + NEGATIVE_OFFSET = 1<<29 + 2, +} + + +INVALID_HANDLE :: ~Handle(0) + + + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_CREATE :: 0x00040 +O_EXCL :: 0x00080 +O_NOCTTY :: 0x00100 +O_TRUNC :: 0x00200 +O_NONBLOCK :: 0x00800 +O_APPEND :: 0x00400 +O_SYNC :: 0x01000 +O_ASYNC :: 0x02000 +O_CLOEXEC :: 0x80000 + + +ERROR_FILE_NOT_FOUND :: Platform_Error.FILE_NOT_FOUND +ERROR_PATH_NOT_FOUND :: Platform_Error.PATH_NOT_FOUND +ERROR_ACCESS_DENIED :: Platform_Error.ACCESS_DENIED +ERROR_INVALID_HANDLE :: Platform_Error.INVALID_HANDLE +ERROR_NOT_ENOUGH_MEMORY :: Platform_Error.NOT_ENOUGH_MEMORY +ERROR_NO_MORE_FILES :: Platform_Error.NO_MORE_FILES +ERROR_HANDLE_EOF :: Platform_Error.HANDLE_EOF +ERROR_NETNAME_DELETED :: Platform_Error.NETNAME_DELETED +ERROR_FILE_EXISTS :: Platform_Error.FILE_EXISTS +ERROR_INVALID_PARAMETER :: Platform_Error.INVALID_PARAMETER +ERROR_BROKEN_PIPE :: Platform_Error.BROKEN_PIPE +ERROR_BUFFER_OVERFLOW :: Platform_Error.BUFFER_OVERFLOW +ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER +ERROR_MOD_NOT_FOUND :: Platform_Error.MOD_NOT_FOUND +ERROR_PROC_NOT_FOUND :: Platform_Error.PROC_NOT_FOUND +ERROR_DIR_NOT_EMPTY :: Platform_Error.DIR_NOT_EMPTY +ERROR_ALREADY_EXISTS :: Platform_Error.ALREADY_EXISTS +ERROR_ENVVAR_NOT_FOUND :: Platform_Error.ENVVAR_NOT_FOUND +ERROR_MORE_DATA :: Platform_Error.MORE_DATA +ERROR_OPERATION_ABORTED :: Platform_Error.OPERATION_ABORTED +ERROR_IO_PENDING :: Platform_Error.IO_PENDING +ERROR_NOT_FOUND :: Platform_Error.NOT_FOUND +ERROR_PRIVILEGE_NOT_HELD :: Platform_Error.PRIVILEGE_NOT_HELD +WSAEACCES :: Platform_Error.WSAEACCES +WSAECONNRESET :: Platform_Error.WSAECONNRESET + +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir + +args: []string + +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { + unimplemented("core:os procedure not supported on JS target") +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +@(require_results) +get_page_size :: proc() -> int { + unimplemented("core:os procedure not supported on JS target") +} + +@(private, require_results) +_processor_core_count :: proc() -> int { + return 1 +} + +exit :: proc "contextless" (code: int) -> ! { + unimplemented_contextless("core:os procedure not supported on JS target") +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return 0 +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return "", false +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} \ No newline at end of file diff --git a/core/os/old/os_linux.odin b/core/os/old/os_linux.odin new file mode 100644 index 000000000..4c32676c6 --- /dev/null +++ b/core/os/old/os_linux.odin @@ -0,0 +1,1233 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +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" + +Handle :: distinct i32 +Pid :: distinct i32 +File_Time :: distinct u64 +Socket :: distinct int + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: linux.Errno +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +EBADF :: Platform_Error.EBADF +EAGAIN :: Platform_Error.EAGAIN +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +EEXIST :: Platform_Error.EEXIST +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EPIPE :: Platform_Error.EPIPE + +ERANGE :: Platform_Error.ERANGE /* Result too large */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock would occur */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ +ENOLCK :: Platform_Error.ENOLCK /* No record locks available */ + +ENOSYS :: Platform_Error.ENOSYS /* Invalid system call number */ + +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ +ELOOP :: Platform_Error.ELOOP /* Too many symbolic links encountered */ +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK /* Operation would block */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ECHRNG :: Platform_Error.ECHRNG /* Channel number out of range */ +EL2NSYNC :: Platform_Error.EL2NSYNC /* Level 2 not synchronized */ +EL3HLT :: Platform_Error.EL3HLT /* Level 3 halted */ +EL3RST :: Platform_Error.EL3RST /* Level 3 reset */ +ELNRNG :: Platform_Error.ELNRNG /* Link number out of range */ +EUNATCH :: Platform_Error.EUNATCH /* Protocol driver not attached */ +ENOCSI :: Platform_Error.ENOCSI /* No CSI structure available */ +EL2HLT :: Platform_Error.EL2HLT /* Level 2 halted */ +EBADE :: Platform_Error.EBADE /* Invalid exchange */ +EBADR :: Platform_Error.EBADR /* Invalid request descriptor */ +EXFULL :: Platform_Error.EXFULL /* Exchange full */ +ENOANO :: Platform_Error.ENOANO /* No anode */ +EBADRQC :: Platform_Error.EBADRQC /* Invalid request code */ +EBADSLT :: Platform_Error.EBADSLT /* Invalid slot */ +EDEADLOCK :: Platform_Error.EDEADLOCK +EBFONT :: Platform_Error.EBFONT /* Bad font file format */ +ENOSTR :: Platform_Error.ENOSTR /* Device not a stream */ +ENODATA :: Platform_Error.ENODATA /* No data available */ +ETIME :: Platform_Error.ETIME /* Timer expired */ +ENOSR :: Platform_Error.ENOSR /* Out of streams resources */ +ENONET :: Platform_Error.ENONET /* Machine is not on the network */ +ENOPKG :: Platform_Error.ENOPKG /* Package not installed */ +EREMOTE :: Platform_Error.EREMOTE /* Object is remote */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EADV :: Platform_Error.EADV /* Advertise error */ +ESRMNT :: Platform_Error.ESRMNT /* Srmount error */ +ECOMM :: Platform_Error.ECOMM /* Communication error on send */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +EDOTDOT :: Platform_Error.EDOTDOT /* RFS specific error */ +EBADMSG :: Platform_Error.EBADMSG /* Not a data message */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large for defined data type */ +ENOTUNIQ :: Platform_Error.ENOTUNIQ /* Name not unique on network */ +EBADFD :: Platform_Error.EBADFD /* File descriptor in bad state */ +EREMCHG :: Platform_Error.EREMCHG /* Remote address changed */ +ELIBACC :: Platform_Error.ELIBACC /* Can not access a needed shared library */ +ELIBBAD :: Platform_Error.ELIBBAD /* Accessing a corrupted shared library */ +ELIBSCN :: Platform_Error.ELIBSCN /* .lib section in a.out corrupted */ +ELIBMAX :: Platform_Error.ELIBMAX /* Attempting to link in too many shared libraries */ +ELIBEXEC :: Platform_Error.ELIBEXEC /* Cannot exec a shared library directly */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ +ERESTART :: Platform_Error.ERESTART /* Interrupted system call should be restarted */ +ESTRPIPE :: Platform_Error.ESTRPIPE /* Streams pipe error */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol not available */ +EPROTONOSUPPOR :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPOR :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported on transport endpoint */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Cannot assign requested address */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection because of reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Transport endpoint is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Transport endpoint is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: cannot splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Connection timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +ESTALE :: Platform_Error.ESTALE /* Stale file handle */ +EUCLEAN :: Platform_Error.EUCLEAN /* Structure needs cleaning */ +ENOTNAM :: Platform_Error.ENOTNAM /* Not a XENIX named type file */ +ENAVAIL :: Platform_Error.ENAVAIL /* No XENIX semaphores available */ +EISNAM :: Platform_Error.EISNAM /* Is a named type file */ +EREMOTEIO :: Platform_Error.EREMOTEIO /* Remote I/O error */ +EDQUOT :: Platform_Error.EDQUOT /* Quota exceeded */ + +ENOMEDIUM :: Platform_Error.ENOMEDIUM /* No medium found */ +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE /* Wrong medium type */ +ECANCELED :: Platform_Error.ECANCELED /* Operation Canceled */ +ENOKEY :: Platform_Error.ENOKEY /* Required key not available */ +EKEYEXPIRED :: Platform_Error.EKEYEXPIRED /* Key has expired */ +EKEYREVOKED :: Platform_Error.EKEYREVOKED /* Key has been revoked */ +EKEYREJECTED :: Platform_Error.EKEYREJECTED /* Key was rejected by service */ + +/* for robust mutexes */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ + +ERFKILL :: Platform_Error.ERFKILL /* Operation not possible due to RF-kill */ + +EHWPOISON :: Platform_Error.EHWPOISON /* Memory page has hardware error */ + +ADDR_NO_RANDOMIZE :: 0x40000 + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_CREATE :: 0x00040 +O_EXCL :: 0x00080 +O_NOCTTY :: 0x00100 +O_TRUNC :: 0x00200 +O_NONBLOCK :: 0x00800 +O_APPEND :: 0x00400 +O_SYNC :: 0x01000 +O_ASYNC :: 0x02000 +O_CLOEXEC :: 0x80000 + + +SEEK_DATA :: 3 +SEEK_HOLE :: 4 +SEEK_MAX :: SEEK_HOLE + + +AF_UNSPEC: int : 0 +AF_UNIX: int : 1 +AF_LOCAL: int : AF_UNIX +AF_INET: int : 2 +AF_INET6: int : 10 +AF_PACKET: int : 17 +AF_BLUETOOTH: int : 31 + +SOCK_STREAM: int : 1 +SOCK_DGRAM: int : 2 +SOCK_RAW: int : 3 +SOCK_RDM: int : 4 +SOCK_SEQPACKET: int : 5 +SOCK_PACKET: int : 10 + +INADDR_ANY: c.ulong : 0 +INADDR_BROADCAST: c.ulong : 0xffffffff +INADDR_NONE: c.ulong : 0xffffffff +INADDR_DUMMY: c.ulong : 0xc0000008 + +IPPROTO_IP: int : 0 +IPPROTO_ICMP: int : 1 +IPPROTO_TCP: int : 6 +IPPROTO_UDP: int : 17 +IPPROTO_IPV6: int : 41 +IPPROTO_ETHERNET: int : 143 +IPPROTO_RAW: int : 255 + +SHUT_RD: int : 0 +SHUT_WR: int : 1 +SHUT_RDWR: int : 2 + + +SOL_SOCKET: int : 1 +SO_DEBUG: int : 1 +SO_REUSEADDR: int : 2 +SO_DONTROUTE: int : 5 +SO_BROADCAST: int : 6 +SO_SNDBUF: int : 7 +SO_RCVBUF: int : 8 +SO_KEEPALIVE: int : 9 +SO_OOBINLINE: int : 10 +SO_LINGER: int : 13 +SO_REUSEPORT: int : 15 +SO_RCVTIMEO_NEW: int : 66 +SO_SNDTIMEO_NEW: int : 67 + +TCP_NODELAY: int : 1 +TCP_CORK: int : 3 + +MSG_TRUNC : int : 0x20 + +// TODO: add remaining fcntl commands +// reference: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h +F_GETFL: int : 3 /* Get file flags */ +F_SETFL: int : 4 /* Set file flags */ + +// NOTE(zangent): These are OS specific! +// Do not mix these up! +RTLD_LAZY :: 0x0001 +RTLD_NOW :: 0x0002 +RTLD_BINDING_MASK :: 0x0003 +RTLD_GLOBAL :: 0x0100 +RTLD_NOLOAD :: 0x0004 +RTLD_DEEPBIND :: 0x0008 +RTLD_NODELETE :: 0x1000 + +socklen_t :: c.int + +Timeval :: struct { + seconds: i64, + microseconds: int, +} + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + +when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + OS_Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + mode: u32, // Mode of the file + nlink: u32, // Number of hard links + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + rdev: u64, // Device ID, if device + _: u64, // Padding + size: i64, // Size of the file, in bytes + block_size: i32, // Optimal blocksize for I/O + _: i32, // Padding + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserved: [2]i32, + } + #assert(size_of(OS_Stat) == 128) +} else { + OS_Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + nlink: u64, // Number of hard links + mode: u32, // Mode of the file + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + _: i32, // 32 bits of padding + rdev: u64, // Device ID, if device + size: i64, // Size of the file, in bytes + block_size: i64, // Optimal bllocksize for I/O + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserved: [3]i64, + } +} + +// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above +Dirent :: struct { + ino: u64, + off: u64, + reclen: u16, + type: u8, + name: [256]byte, +} + +ADDRESS_FAMILY :: u16 +SOCKADDR :: struct #packed { + sa_family: ADDRESS_FAMILY, + sa_data: [14]c.char, +} + +SOCKADDR_STORAGE_LH :: struct #packed { + ss_family: ADDRESS_FAMILY, + __ss_pad1: [6]c.char, + __ss_align: i64, + __ss_pad2: [112]c.char, +} + +sockaddr_in :: struct #packed { + sin_family: ADDRESS_FAMILY, + sin_port: u16be, + sin_addr: in_addr, + sin_zero: [8]c.char, +} + +sockaddr_in6 :: struct #packed { + sin6_family: ADDRESS_FAMILY, + sin6_port: u16be, + sin6_flowinfo: c.ulong, + sin6_addr: in6_addr, + sin6_scope_id: c.ulong, +} + +in_addr :: struct #packed { + s_addr: u32, +} + +in6_addr :: struct #packed { + s6_addr: [16]u8, +} + +rtnl_link_stats :: struct #packed { + rx_packets: u32, + tx_packets: u32, + rx_bytes: u32, + tx_bytes: u32, + rx_errors: u32, + tx_errors: u32, + rx_dropped: u32, + tx_dropped: u32, + multicast: u32, + collisions: u32, + rx_length_errors: u32, + rx_over_errors: u32, + rx_crc_errors: u32, + rx_frame_errors: u32, + rx_fifo_errors: u32, + rx_missed_errors: u32, + tx_aborted_errors: u32, + tx_carrier_errors: u32, + tx_fifo_errors: u32, + tx_heartbeat_errors: u32, + tx_window_errors: u32, + rx_compressed: u32, + tx_compressed: u32, + rx_nohandler: u32, +} + +SIOCGIFFLAG :: enum c.int { + UP = 0, /* Interface is up. */ + BROADCAST = 1, /* Broadcast address valid. */ + DEBUG = 2, /* Turn on debugging. */ + LOOPBACK = 3, /* Is a loopback net. */ + POINT_TO_POINT = 4, /* Interface is point-to-point link. */ + NO_TRAILERS = 5, /* Avoid use of trailers. */ + RUNNING = 6, /* Resources allocated. */ + NOARP = 7, /* No address resolution protocol. */ + PROMISC = 8, /* Receive all packets. */ + ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */ + MASTER = 10, /* Master of a load balancer. */ + SLAVE = 11, /* Slave of a load balancer. */ + MULTICAST = 12, /* Supports multicast. */ + PORTSEL = 13, /* Can set media type. */ + AUTOMEDIA = 14, /* Auto media select active. */ + DYNAMIC = 15, /* Dialup device with changing addresses. */ + LOWER_UP = 16, + DORMANT = 17, + ECHO = 18, +} +SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] + +ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: SIOCGIFFLAGS, + address: ^SOCKADDR, + netmask: ^SOCKADDR, + broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address + data: rawptr, // Address-specific data. +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + + +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +AT_FDCWD :: ~uintptr(99) /* -100 */ +AT_REMOVEDIR :: uintptr(0x200) +AT_SYMLINK_NOFOLLOW :: uintptr(0x100) + +pollfd :: struct { + fd: c.int, + events: c.short, + revents: c.short, +} + +sigset_t :: distinct u64 + +foreign libc { + @(link_name="__errno_location") __errno_location :: proc() -> ^c.int --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="get_nprocs") _unix_get_nprocs :: proc() -> c.int --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> c.int --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- + @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + + @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- + @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- +} + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +// determine errno from syscall return value +@(private, require_results) +_get_errno :: proc(res: int) -> Error { + if res < 0 && res > -4096 { + return Platform_Error(-res) + } + return nil +} + +// get errno from libc +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := Platform_Error(__errno_location()^) + #partial switch err { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + } + return err +} + +personality :: proc(persona: u64) -> Error { + res := unix.sys_personality(persona) + if res == -1 { + return _get_errno(res) + } + return nil +} + +@(require_results) +fork :: proc() -> (Pid, Error) { + pid := unix.sys_fork() + if pid == -1 { + return -1, _get_errno(pid) + } + return Pid(pid), nil +} + +execvp :: proc(path: string, args: []string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + args_cstrs := make([]cstring, len(args) + 2, context.temp_allocator) + args_cstrs[0] = strings.clone_to_cstring(path, context.temp_allocator) + for i := 0; i < len(args); i += 1 { + args_cstrs[i+1] = strings.clone_to_cstring(args[i], context.temp_allocator) + } + + _unix_execvp(path_cstr, raw_data(args_cstrs)) + return get_last_error() +} + + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := unix.sys_open(cstr, flags, uint(mode)) + if handle < 0 { + return INVALID_HANDLE, _get_errno(handle) + } + return Handle(handle), nil +} + +close :: proc(fd: Handle) -> Error { + return _get_errno(unix.sys_close(int(fd))) +} + +flush :: proc(fd: Handle) -> Error { + return _get_errno(unix.sys_fsync(int(fd))) +} + +// If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). +// `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := unix.sys_read(int(fd), raw_data(data), to_read) + if bytes_read < 0 { + return -1, _get_errno(bytes_read) + } + return bytes_read, nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := unix.sys_write(int(fd), raw_data(data), to_write) + if bytes_written < 0 { + return -1, _get_errno(bytes_written) + } + return bytes_written, nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := unix.sys_pread(int(fd), raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, _get_errno(bytes_read) + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := unix.sys_pwrite(int(fd), raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, _get_errno(bytes_written) + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := unix.sys_lseek(int(fd), offset, whence) + if res < 0 { + errno := _get_errno(int(res)) + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return i64(res), nil +} + +@(require_results, no_sanitize_memory) +file_size :: proc(fd: Handle) -> (i64, Error) { + // deliberately uninitialized; the syscall fills this buffer for us + s: OS_Stat = --- + result := unix.sys_fstat(int(fd), rawptr(&s)) + if result < 0 { + return 0, _get_errno(result) + } + return max(s.size, 0), nil +} + +rename :: proc(old_path, new_path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr)) +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _get_errno(unix.sys_unlink(path_cstr)) +} + +make_directory :: proc(path: string, mode: u32 = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _get_errno(unix.sys_mkdir(path_cstr, uint(mode))) +} + +remove_directory :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _get_errno(unix.sys_rmdir(path_cstr)) +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +@(require_results) +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := unix.sys_access(cpath, O_RDONLY) + return res == 0 +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +/* TODO(zangent): Implement these! +last_write_time :: proc(fd: Handle) -> File_Time {} +last_write_time_by_name :: proc(name: string) -> File_Time {} +*/ +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(private, require_results, no_sanitize_memory) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized; the syscall fills this buffer for us + s: OS_Stat = --- + result := unix.sys_stat(cstr, &s) + if result < 0 { + return s, _get_errno(result) + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized; the syscall fills this buffer for us + s: OS_Stat = --- + result := unix.sys_lstat(cstr, &s) + if result < 0 { + return s, _get_errno(result) + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + // deliberately uninitialized; the syscall fills this buffer for us + s: OS_Stat = --- + result := unix.sys_fstat(int(fd), rawptr(&s)) + if result < 0 { + return s, _get_errno(result) + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + err = nil + + if result == nil { + end_of_stream = true + return + } + end_of_stream = false + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = 256 + buf := make([]byte, bufsz) + for { + rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) + if rc < 0 { + delete(buf) + return "", _get_errno(rc) + } else if rc == int(bufsz) { + // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? + bufsz *= 2 + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup, err := linux.dup(linux.Fd(fd)) + return Handle(dup), err +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + buf : [256]byte + fd_str := strconv.write_int( buf[:], cast(i64)fd, 10 ) + + procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } ) + defer delete(procfs_path) + + return _readlink(procfs_path) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := unix.sys_access(cstr, mask) + if result < 0 { + return false, _get_errno(result) + } + return true, nil +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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 :: proc(key, value: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) + // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly + res := _unix_setenv(key_cstring, value_cstring, 1) + if res < 0 { + return get_last_error() + } + return nil +} + +unset_env :: proc(key: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_putenv(s) + if res < 0 { + return get_last_error() + } + return nil +} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + for { + #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) + + if res >= 0 { + return strings.string_from_null_terminated_ptr(&buf[0], len(buf)) + } + if _get_errno(res) != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := unix.sys_chdir(cstr) + if res < 0 { + return _get_errno(res) + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return unix.sys_gettid() +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private, require_results) +_processor_core_count :: proc() -> int { + return int(_unix_get_nprocs()) +} + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for _, i in res { + res[i] = string(runtime.args__[i]) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + +@(require_results) +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { + result := unix.sys_socket(domain, type, protocol) + if result < 0 { + return 0, _get_errno(result) + } + return Socket(result), nil +} + +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { + result := unix.sys_bind(int(sd), addr, len) + if result < 0 { + return _get_errno(result) + } + return nil +} + + +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { + result := unix.sys_connect(int(sd), addr, len) + if result < 0 { + return _get_errno(result) + } + return nil +} + +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { + result := unix.sys_accept(int(sd), rawptr(addr), len) + if result < 0 { + return 0, _get_errno(result) + } + return Socket(result), nil +} + +listen :: proc(sd: Socket, backlog: int) -> Error { + result := unix.sys_listen(int(sd), backlog) + if result < 0 { + return _get_errno(result) + } + return nil +} + +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { + result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen) + if result < 0 { + return _get_errno(result) + } + return nil +} + + +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { + result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size)) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), nil +} + +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), nil +} + + +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { + result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), nil +} + +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), nil +} + +shutdown :: proc(sd: Socket, how: int) -> Error { + result := unix.sys_shutdown(int(sd), how) + if result < 0 { + return _get_errno(result) + } + return nil +} + +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { + result := unix.sys_fcntl(fd, cmd, arg) + if result < 0 { + return 0, _get_errno(result) + } + return result, nil +} + +@(require_results) +poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) { + result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout) + if result < 0 { + return 0, _get_errno(result) + } + return result, nil +} + +@(require_results) +ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) { + result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t)) + if result < 0 { + return 0, _get_errno(result) + } + return result, nil +} diff --git a/core/os/old/os_netbsd.odin b/core/os/old/os_netbsd.odin new file mode 100644 index 000000000..640ea46cd --- /dev/null +++ b/core/os/old/os_netbsd.odin @@ -0,0 +1,1032 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +import "base:runtime" +import "core:strings" +import "core:c" + +Handle :: distinct i32 +File_Time :: distinct u64 + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + EINTR = 4, /* Interrupted system call */ + ESRCH = 3, /* No such process */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided. 11 was EAGAIN */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large or too small */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol option not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + EOPNOTSUPP = 45, /* Operation not supported */ + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* SystemV IPC */ + EIDRM = 82, /* Identifier removed */ + ENOMSG = 83, /* No message of desired type */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ + EILSEQ = 85, /* Illegal byte sequence */ + + /* From IEEE Std 1003.1-2001 */ + /* Base, Realtime, Threads or Thread Priority Scheduling option errors */ + ENOTSUP = 86, /* Not supported */ + + /* Realtime option errors */ + ECANCELED = 87, /* Operation canceled */ + + /* Realtime, XSI STREAMS option errors */ + EBADMSG = 88, /* Bad or Corrupt message */ + + /* XSI STREAMS option errors */ + ENODATA = 89, /* No message available */ + ENOSR = 90, /* No STREAM resources */ + ENOSTR = 91, /* Not a STREAM */ + ETIME = 92, /* STREAM ioctl timeout */ + + /* File system extended attribute errors */ + ENOATTR = 93, /* Attribute not found */ + + /* Realtime, XSI STREAMS option errors */ + EMULTIHOP = 94, /* Multihop attempted */ + ENOLINK = 95, /* Link has been severed */ + EPROTO = 96, /* Protocol error */ + + /* Robust mutexes */ + EOWNERDEAD = 97, /* Previous owner died */ + ENOTRECOVERABLE = 98, /* State not recoverable */ + + ELAST = 98, /* Must equal largest Error */ +} + +EPERM :: Platform_Error.EPERM /* Operation not permitted */ +ENOENT :: Platform_Error.ENOENT /* No such file or directory */ +EINTR :: Platform_Error.EINTR /* Interrupted system call */ +ESRCH :: Platform_Error.ESRCH /* No such process */ +EIO :: Platform_Error.EIO /* Input/output error */ +ENXIO :: Platform_Error.ENXIO /* Device not configured */ +E2BIG :: Platform_Error.E2BIG /* Argument list too long */ +ENOEXEC :: Platform_Error.ENOEXEC /* Exec format error */ +EBADF :: Platform_Error.EBADF /* Bad file descriptor */ +ECHILD :: Platform_Error.ECHILD /* No child processes */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM :: Platform_Error.ENOMEM /* Cannot allocate memory */ +EACCES :: Platform_Error.EACCES /* Permission denied */ +EFAULT :: Platform_Error.EFAULT /* Bad address */ +ENOTBLK :: Platform_Error.ENOTBLK /* Block device required */ +EBUSY :: Platform_Error.EBUSY /* Device busy */ +EEXIST :: Platform_Error.EEXIST /* File exists */ +EXDEV :: Platform_Error.EXDEV /* Cross-device link */ +ENODEV :: Platform_Error.ENODEV /* Operation not supported by device */ +ENOTDIR :: Platform_Error.ENOTDIR /* Not a directory */ +EISDIR :: Platform_Error.EISDIR /* Is a directory */ +EINVAL :: Platform_Error.EINVAL /* Invalid argument */ +ENFILE :: Platform_Error.ENFILE /* Too many open files in system */ +EMFILE :: Platform_Error.EMFILE /* Too many open files */ +ENOTTY :: Platform_Error.ENOTTY /* Inappropriate ioctl for device */ +ETXTBSY :: Platform_Error.ETXTBSY /* Text file busy */ +EFBIG :: Platform_Error.EFBIG /* File too large */ +ENOSPC :: Platform_Error.ENOSPC /* No space left on device */ +ESPIPE :: Platform_Error.ESPIPE /* Illegal seek */ +EROFS :: Platform_Error.EROFS /* Read-only file system */ +EMLINK :: Platform_Error.EMLINK /* Too many links */ +EPIPE :: Platform_Error.EPIPE /* Broken pipe */ + +/* math software */ +EDOM :: Platform_Error.EDOM /* Numerical argument out of domain */ +ERANGE :: Platform_Error.ERANGE /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +EAGAIN :: Platform_Error.EAGAIN /* Resource temporarily unavailable */ +EWOULDBLOCK :: EAGAIN /* Operation would block */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol option not available */ +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol family */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection on reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Socket is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Socket is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Can't send after socket shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: can't splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Operation timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ + +ELOOP :: Platform_Error.ELOOP /* Too many levels of symbolic links */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM :: Platform_Error.EPROCLIM /* Too many processes */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +EDQUOT :: Platform_Error.EDQUOT /* Disc quota exceeded */ + +/* Network File System */ +ESTALE :: Platform_Error.ESTALE /* Stale NFS file handle */ +EREMOTE :: Platform_Error.EREMOTE /* Too many levels of remote in path */ +EBADRPC :: Platform_Error.EBADRPC /* RPC struct is bad */ +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH /* RPC version wrong */ +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL /* RPC prog. not avail */ +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH /* Program version wrong */ +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL /* Bad procedure for program */ + +ENOLCK :: Platform_Error.ENOLCK /* No locks available */ +ENOSYS :: Platform_Error.ENOSYS /* Function not implemented */ + +EFTYPE :: Platform_Error.EFTYPE /* Inappropriate file type or format */ +EAUTH :: Platform_Error.EAUTH /* Authentication error */ +ENEEDAUTH :: Platform_Error.ENEEDAUTH /* Need authenticator */ + +/* SystemV IPC */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +ENOTSUP :: Platform_Error.ENOTSUP /* Not supported */ + +/* Realtime option errors */ +ECANCELED :: Platform_Error.ECANCELED /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +EBADMSG :: Platform_Error.EBADMSG /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +ENODATA :: Platform_Error.ENODATA /* No message available */ +ENOSR :: Platform_Error.ENOSR /* No STREAM resources */ +ENOSTR :: Platform_Error.ENOSTR /* Not a STREAM */ +ETIME :: Platform_Error.ETIME /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +ENOATTR :: Platform_Error.ENOATTR /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ + +/* Robust mutexes */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Previous owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ + +ELAST :: Platform_Error.ELAST /* Must equal largest Error */ + +/* end of Error */ + +O_RDONLY :: 0x000000000 +O_WRONLY :: 0x000000001 +O_RDWR :: 0x000000002 +O_CREATE :: 0x000000200 +O_EXCL :: 0x000000800 +O_NOCTTY :: 0x000008000 +O_TRUNC :: 0x000000400 +O_NONBLOCK :: 0x000000004 +O_APPEND :: 0x000000008 +O_SYNC :: 0x000000080 +O_ASYNC :: 0x000000040 +O_CLOEXEC :: 0x000400000 + +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +RTLD_GLOBAL :: 0x100 +RTLD_LOCAL :: 0x200 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x01000 +RTLD_NOLOAD :: 0x02000 + +F_GETPATH :: 15 + +MAX_PATH :: 1024 +MAXNAMLEN :: 511 + +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +dev_t :: u64 +ino_t :: u64 +nlink_t :: u32 +off_t :: i64 +mode_t :: u32 +pid_t :: u32 +uid_t :: u32 +gid_t :: u32 +blkcnt_t :: i64 +blksize_t :: i32 +fflags_t :: u32 +time_t :: i64 + +OS_Stat :: struct { + device_id: dev_t, + mode: mode_t, + _padding0: i16, + ino: ino_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + _padding1: i32, + rdev: dev_t, + + last_access: Unix_File_Time, + modified: Unix_File_Time, + status_change: Unix_File_Time, + birthtime: Unix_File_Time, + + size: off_t, + blocks: blkcnt_t, + block_size: blksize_t, + + flags: fflags_t, + gen: u32, + lspare: [2]u32, +} + +Dirent :: struct { + ino: ino_t, + reclen: u16, + namlen: u16, + type: u8, + name: [MAXNAMLEN + 1]byte, +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + +@(require_results) S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +foreign libc { + @(link_name="__errno") __errno_location :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(link_name="__lstat50") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="__fstat50") _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="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="__readdir_r30") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} + +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +@(private) +foreign libc { + _lwp_self :: proc() -> i32 --- +} + +// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__errno_location()^) +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return +} + +rename :: proc(old_path, new_path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return get_last_error() + } + return nil +} + +remove_directory :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +@(require_results) +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} + +@(require_results) +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { + result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) + if result < 0 { + return 0, get_last_error() + } + return int(result), nil +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(private, require_results, no_sanitize_memory) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + err = nil + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } + + return "", Error{} +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { + buf: [MAX_PATH]byte + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + #no_bounds_check for { + cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if get_last_error() != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return int(_lwp_self()) +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} + +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} + +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} + +@(require_results) +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private, require_results) +_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 +} + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for _, i in res { + res[i] = string(runtime.args__[i]) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/old/os_openbsd.odin b/core/os/old/os_openbsd.odin new file mode 100644 index 000000000..bf89a21f4 --- /dev/null +++ b/core/os/old/os_openbsd.odin @@ -0,0 +1,932 @@ +package os + +foreign import libc "system:c" + +import "core:strings" +import "core:c" +import "base:runtime" + +Handle :: distinct i32 +Pid :: distinct i32 +File_Time :: distinct u64 + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EDEADLK = 11, + ENOMEM = 12, + EACCES = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, + EAGAIN = 35, + EWOULDBLOCK = EAGAIN, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETOOMANYREFS = 59, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLONG = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EREMOTE = 71, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIPSEC = 82, + ENOATTR = 83, + EILSEQ = 84, + ENOMEDIUM = 85, + EMEDIUMTYPE = 86, + EOVERFLOW = 87, + ECANCELED = 88, + EIDRM = 89, + ENOMSG = 90, + ENOTSUP = 91, + EBADMSG = 92, + ENOTRECOVERABLE = 93, + EOWNERDEAD = 94, + EPROTO = 95, +} + +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EDEADLK :: Platform_Error.EDEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EREMOTE :: Platform_Error.EREMOTE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIPSEC :: Platform_Error.EIPSEC +ENOATTR :: Platform_Error.ENOATTR +EILSEQ :: Platform_Error.EILSEQ +ENOMEDIUM :: Platform_Error.ENOMEDIUM +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +ENOTSUP :: Platform_Error.ENOTSUP +EBADMSG :: Platform_Error.EBADMSG +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD +EPROTO :: Platform_Error.EPROTO + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_NONBLOCK :: 0x00004 +O_APPEND :: 0x00008 +O_ASYNC :: 0x00040 +O_SYNC :: 0x00080 +O_CREATE :: 0x00200 +O_TRUNC :: 0x00400 +O_EXCL :: 0x00800 +O_NOCTTY :: 0x08000 +O_CLOEXEC :: 0x10000 + +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +RTLD_LOCAL :: 0x000 +RTLD_GLOBAL :: 0x100 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x400 + +MAX_PATH :: 1024 + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +pid_t :: i32 +time_t :: i64 +mode_t :: u32 +dev_t :: i32 +ino_t :: u64 +nlink_t :: u32 +uid_t :: u32 +gid_t :: u32 +off_t :: i64 +blkcnt_t :: u64 +blksize_t :: i32 + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +OS_Stat :: struct { + mode: mode_t, // inode protection mode + device_id: dev_t, // inode's device + serial: ino_t, // inode's number + nlink: nlink_t, // number of hard links + uid: uid_t, // user ID of the file's owner + gid: gid_t, // group ID of the file's group + rdev: dev_t, // device type + + last_access: Unix_File_Time, // time of last access + modified: Unix_File_Time, // time of last data modification + status_change: Unix_File_Time, // time of last file status change + + size: off_t, // file size, in bytes + blocks: blkcnt_t, // blocks allocated for file + block_size: blksize_t, // optimal blocksize for I/O + + flags: u32, // user defined flags for file + gen: u32, // file generation number + birthtime: Unix_File_Time, // time of file creation +} + +MAXNAMLEN :: 255 + +// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above +Dirent :: struct { + ino: ino_t, // file number of entry + off: off_t, // offset after this entry + reclen: u16, // length of this record + type: u8, // file type + namlen: u8, // length of string in name + _padding: [4]u8, + name: [MAXNAMLEN + 1]byte, // name +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket +S_ISVTX :: 0o001000 // Save swapped text even after use + +// File mode + // Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + + // Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + + // Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISTXT :: 0o1000 // Sticky bit + +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0x00 // Test for file existance +X_OK :: 0x01 // Test for execute permission +W_OK :: 0x02 // Test for write permission +R_OK :: 0x04 // Test for read permission + +AT_FDCWD :: -100 +AT_EACCESS :: 0x01 +AT_SYMLINK_NOFOLLOW :: 0x02 +AT_SYMLINK_FOLLOW :: 0x04 +AT_REMOVEDIR :: 0x08 + +@(default_calling_convention="c") +foreign libc { + @(link_name="__errno") __error :: proc() -> ^c.int --- + + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) +} + +@(require_results) +fork :: proc() -> (Pid, Error) { + pid := _unix_fork() + if pid == -1 { + return Pid(-1), get_last_error() + } + return Pid(pid), nil +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return +} + +rename :: proc(old_path, new_path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return get_last_error() + } + return nil +} + +remove_directory :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +/* TODO(zangent): Implement these! +last_write_time :: proc(fd: Handle) -> File_Time {} +last_write_time_by_name :: proc(name: string) -> File_Time {} +*/ +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(private, require_results, no_sanitize_memory) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_stat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results, no_sanitize_memory) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_fstat(fd, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + err = nil + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + +// XXX OpenBSD +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + rel := rel + if rel == "" { + rel = "." + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cstr, c.int(mask)) + if res == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + buf[len(key)] = 0 + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + buf := make([dynamic]u8, MAX_PATH) + for { + cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if get_last_error() != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf) + MAX_PATH) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return _unix_getthrid() +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} +@(require_results) +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +_SC_NPROCESSORS_ONLN :: 503 + +@(private, require_results) +_processor_core_count :: proc() -> int { + return int(_sysconf(_SC_NPROCESSORS_ONLN)) +} + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + res := make([]string, len(runtime.args__)) + for _, i in res { + res[i] = string(runtime.args__[i]) + } + return res +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/old/os_wasi.odin b/core/os/old/os_wasi.odin new file mode 100644 index 000000000..fe0a1fb3e --- /dev/null +++ b/core/os/old/os_wasi.odin @@ -0,0 +1,273 @@ +package os + +import "core:sys/wasm/wasi" +import "base:runtime" + +Handle :: distinct i32 +_Platform_Error :: wasi.errno_t + +INVALID_HANDLE :: -1 + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_CREATE :: 0x00040 +O_EXCL :: 0x00080 +O_NOCTTY :: 0x00100 +O_TRUNC :: 0x00200 +O_NONBLOCK :: 0x00800 +O_APPEND :: 0x00400 +O_SYNC :: 0x01000 +O_ASYNC :: 0x02000 +O_CLOEXEC :: 0x80000 + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +args := _alloc_command_line_arguments() + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + cmd_args := make([]string, len(runtime.args__)) + for &arg, i in cmd_args { + arg = string(runtime.args__[i]) + } + return cmd_args +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). + +@(private) +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +@(private) +preopens: []Preopen + +@(init, private) +init_preopens :: proc "contextless" () { + strip_prefixes :: proc "contextless"(path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + context = runtime.default_context() + + dyn_preopens: [dynamic]Preopen + loop: for fd := wasi.fd_t(3); ; fd += 1 { + desc, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break loop + case: panic("fd_prestat_get returned an unexpected error") + case .SUCCESS: + } + + switch desc.tag { + case .DIR: + buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens") + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + panic("could not get filesystem preopen dir name") + } + append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) + } + } + preopens = dyn_preopens[:] +} + +@(require_results) +wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} + +write :: proc(fd: Handle, data: []byte) -> (int, Errno) { + iovs := wasi.ciovec_t(data) + n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) + return int(n), Platform_Error(err) +} +read :: proc(fd: Handle, data: []byte) -> (int, Errno) { + iovs := wasi.iovec_t(data) + n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) + return int(n), Platform_Error(err) +} +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + iovs := wasi.ciovec_t(data) + n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) + return int(n), Platform_Error(err) +} +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + iovs := wasi.iovec_t(data) + n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) + return int(n), Platform_Error(err) +} +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { + oflags: wasi.oflags_t + if mode & O_CREATE == O_CREATE { + oflags += {.CREATE} + } + if mode & O_EXCL == O_EXCL { + oflags += {.EXCL} + } + if mode & O_TRUNC == O_TRUNC { + oflags += {.TRUNC} + } + + rights: wasi.rights_t = {.FD_SEEK, .FD_FILESTAT_GET} + switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: rights += {.FD_READ} + case O_WRONLY: rights += {.FD_WRITE} + case O_RDWR: rights += {.FD_READ, .FD_WRITE} + } + + fdflags: wasi.fdflags_t + if mode & O_APPEND == O_APPEND { + fdflags += {.APPEND} + } + if mode & O_NONBLOCK == O_NONBLOCK { + fdflags += {.NONBLOCK} + } + if mode & O_SYNC == O_SYNC { + fdflags += {.SYNC} + } + + dir_fd, relative, ok := wasi_match_preopen(path) + if !ok { + return INVALID_HANDLE, Errno(wasi.errno_t.BADF) + } + + fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + return Handle(fd), Platform_Error(err) +} +close :: proc(fd: Handle) -> Errno { + err := wasi.fd_close(wasi.fd_t(fd)) + return Platform_Error(err) +} + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { + n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) + return i64(n), Platform_Error(err) +} +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return 0 +} +@(private, require_results) +_processor_core_count :: proc() -> int { + return 1 +} + +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Errno) { + stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return + size = i64(stat.size) + return +} + + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + wasi.proc_exit(wasi.exitcode_t(code)) +} + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return "", false +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(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} \ No newline at end of file diff --git a/core/os/old/os_windows.odin b/core/os/old/os_windows.odin new file mode 100644 index 000000000..cb7e42f67 --- /dev/null +++ b/core/os/old/os_windows.odin @@ -0,0 +1,871 @@ +#+build windows +package os + +import win32 "core:sys/windows" +import "base:runtime" +import "base:intrinsics" +import "core:unicode/utf16" + +Handle :: distinct uintptr +File_Time :: distinct u64 + +INVALID_HANDLE :: ~Handle(0) + +O_RDONLY :: 0x00000 +O_WRONLY :: 0x00001 +O_RDWR :: 0x00002 +O_CREATE :: 0x00040 +O_EXCL :: 0x00080 +O_NOCTTY :: 0x00100 +O_TRUNC :: 0x00200 +O_NONBLOCK :: 0x00800 +O_APPEND :: 0x00400 +O_SYNC :: 0x01000 +O_ASYNC :: 0x02000 +O_CLOEXEC :: 0x80000 + +_Platform_Error :: win32.System_Error + +ERROR_FILE_NOT_FOUND :: _Platform_Error(2) +ERROR_PATH_NOT_FOUND :: _Platform_Error(3) +ERROR_ACCESS_DENIED :: _Platform_Error(5) +ERROR_INVALID_HANDLE :: _Platform_Error(6) +ERROR_NOT_ENOUGH_MEMORY :: _Platform_Error(8) +ERROR_NO_MORE_FILES :: _Platform_Error(18) +ERROR_HANDLE_EOF :: _Platform_Error(38) +ERROR_NETNAME_DELETED :: _Platform_Error(64) +ERROR_FILE_EXISTS :: _Platform_Error(80) +ERROR_INVALID_PARAMETER :: _Platform_Error(87) +ERROR_BROKEN_PIPE :: _Platform_Error(109) +ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) +ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) +ERROR_MOD_NOT_FOUND :: _Platform_Error(126) +ERROR_PROC_NOT_FOUND :: _Platform_Error(127) +ERROR_NEGATIVE_SEEK :: _Platform_Error(131) +ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) +ERROR_ALREADY_EXISTS :: _Platform_Error(183) +ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) +ERROR_MORE_DATA :: _Platform_Error(234) +ERROR_OPERATION_ABORTED :: _Platform_Error(995) +ERROR_IO_PENDING :: _Platform_Error(997) +ERROR_NOT_FOUND :: _Platform_Error(1168) +ERROR_PRIVILEGE_NOT_HELD :: _Platform_Error(1314) +WSAEACCES :: _Platform_Error(10013) +WSAECONNRESET :: _Platform_Error(10054) + +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := win32.GetLastError() + if err == 0 { + return nil + } + switch err { + case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: + return .Permission_Denied + + case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: + return .Exist + + case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: + return .Not_Exist + + case win32.ERROR_NO_DATA: + return .Closed + + case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: + return .Timeout + + case win32.ERROR_NOT_SUPPORTED: + return .Unsupported + + case win32.ERROR_HANDLE_EOF: + return .EOF + + case win32.ERROR_INVALID_HANDLE: + return .Invalid_File + + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + + case + win32.ERROR_BAD_ARGUMENTS, + win32.ERROR_INVALID_PARAMETER, + win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_NO_MORE_FILES, + win32.ERROR_LOCK_VIOLATION, + win32.ERROR_BROKEN_PIPE, + win32.ERROR_CALL_NOT_IMPLEMENTED, + win32.ERROR_INSUFFICIENT_BUFFER, + win32.ERROR_INVALID_NAME, + win32.ERROR_LOCK_FAILED, + win32.ERROR_ENVVAR_NOT_FOUND, + win32.ERROR_OPERATION_ABORTED, + win32.ERROR_IO_PENDING, + win32.ERROR_NO_UNICODE_TRANSLATION: + // fallthrough + } + return Platform_Error(err) +} + + +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { + file_info: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) { + return 0, get_last_error() + } + lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime) + hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime) + return lo | hi << 32, nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { + data: win32.WIN32_FILE_ATTRIBUTE_DATA + + wide_path := win32.utf8_to_wstring(name) + if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) { + return 0, get_last_error() + } + + l := File_Time(data.ftLastWriteTime.dwLowDateTime) + h := File_Time(data.ftLastWriteTime.dwHighDateTime) + return l | h << 32, nil +} + + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + info: win32.SYSTEM_INFO + win32.GetSystemInfo(&info) + page_size = int(info.dwPageSize) + return page_size +} + +@(private, require_results) +_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 +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + win32.ExitProcess(win32.DWORD(code)) +} + + + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return int(win32.GetCurrentThreadId()) +} + + + +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + arg_count: i32 + arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) + arg_list := make([]string, int(arg_count)) + for _, i in arg_list { + wc_str := (^win32.wstring)(uintptr(arg_list_ptr) + size_of(win32.wstring)*uintptr(i))^ + olen := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1, + nil, 0, nil, nil) + + buf := make([]byte, int(olen)) + n := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1, + raw_data(buf), olen, nil, nil) + if n > 0 { + n -= 1 + } + arg_list[i] = string(buf[:n]) + } + + return arg_list +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + for s in args { + delete(s) + } + delete(args) +} + +/* + Windows 11 (preview) has the same major and minor version numbers + as Windows 10: 10 and 0 respectively. + + To determine if you're on Windows 10 or 11, we need to look at + the build number. As far as we can tell right now, the cutoff is build 22_000. + + TODO: Narrow down this range once Win 11 is published and the last Win 10 builds + become available. +*/ +WINDOWS_11_BUILD_CUTOFF :: 22_000 + +@(require_results) +get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW { + osvi : win32.OSVERSIONINFOEXW + osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW) + win32.RtlGetVersion(&osvi) + return osvi +} + +@(require_results) +is_windows_xp :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) +} + +@(require_results) +is_windows_vista :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) +} + +@(require_results) +is_windows_7 :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) +} + +@(require_results) +is_windows_8 :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) +} + +@(require_results) +is_windows_8_1 :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) +} + +@(require_results) +is_windows_10 :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < WINDOWS_11_BUILD_CUTOFF) +} + +@(require_results) +is_windows_11 :: proc "contextless" () -> bool { + osvi := get_windows_version_w() + return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF) +} + +@(require_results) +is_path_separator :: proc(c: byte) -> bool { + return c == '/' || c == '\\' +} + +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { + if len(path) == 0 { + return INVALID_HANDLE, General_Error.Not_Exist + } + + access: u32 + switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: access = win32.FILE_GENERIC_READ + case O_WRONLY: access = win32.FILE_GENERIC_WRITE + case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE + } + + if mode&O_CREATE != 0 { + access |= win32.FILE_GENERIC_WRITE + } + if mode&O_APPEND != 0 { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + + share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE + sa: ^win32.SECURITY_ATTRIBUTES = nil + sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} + if mode&O_CLOEXEC == 0 { + sa = &sa_inherit + } + + create_mode: u32 + switch { + case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): + create_mode = win32.CREATE_NEW + case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): + create_mode = win32.CREATE_ALWAYS + case mode&O_CREATE == O_CREATE: + create_mode = win32.OPEN_ALWAYS + case mode&O_TRUNC == O_TRUNC: + create_mode = win32.TRUNCATE_EXISTING + case: + create_mode = win32.OPEN_EXISTING + } + + attrs := win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS + if mode & (O_NONBLOCK) == O_NONBLOCK { + attrs |= win32.FILE_FLAG_OVERLAPPED + } + + wide_path := win32.utf8_to_wstring(path) + handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, attrs, nil)) + if handle != INVALID_HANDLE { + return handle, nil + } + + return INVALID_HANDLE, get_last_error() +} + +close :: proc(fd: Handle) -> Error { + if !win32.CloseHandle(win32.HANDLE(fd)) { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> (err: Error) { + if !win32.FlushFileBuffers(win32.HANDLE(fd)) { + err = get_last_error() + } + return +} + + + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + 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), get_last_error() + } + total_write += i64(single_write_length) + } + return int(total_write), nil +} + +@(private="file", require_results) +read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } + + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 + + for n < len(b) && err == nil { + min_read := max(len(b)/4, 1 if len(b) > 0 else 0) + max_read := u32(min(BUF_SIZE, min_read)) + if max_read == 0 { + break + } + + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = get_last_error() + } + + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] + + ctrl_z := false + for i := 0; i < len(src) && n < len(b); i += 1 { + x := src[i] + if x == 0x1a { // ctrl-z + ctrl_z = true + break + } + b[n] = x + n += 1 + } + if ctrl_z || single_read_length < max_read { + break + } + + // NOTE(bill): if the last two values were a newline, then it is expected that + // this is the end of the input + if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { + break + } + + } + + return +} + +read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + handle := win32.HANDLE(fd) + + m: u32 + is_console := win32.GetConsoleMode(handle, &m) + length := len(data) + + // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. + to_read := min(i64(length), MAX_RW) + + if is_console { + total_read, err = read_console(handle, data[total_read:][:to_read]) + if err != nil { + return total_read, err + } + } else { + // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) + bytes_read: win32.DWORD + if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e { + // Successful read can mean two things, including EOF, see: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + if bytes_read == 0 { + return 0, .EOF + } else { + return int(bytes_read), nil + } + } else { + return 0, get_last_error() + } + } + return total_read, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + w: u32 + switch whence { + case 0: w = win32.FILE_BEGIN + case 1: w = win32.FILE_CURRENT + case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence + } + hi := i32(offset>>32) + lo := i32(offset) + ft := win32.GetFileType(win32.HANDLE(fd)) + if ft == win32.FILE_TYPE_PIPE { + return 0, .File_Is_Pipe + } + + dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + err := get_last_error() + return 0, err + } + return i64(hi)<<32 + i64(dw_ptr), nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + length: win32.LARGE_INTEGER + err: Error + if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { + err = get_last_error() + } + return i64(length), err +} + + +@(private) +MAX_RW :: 1<<30 + +@(private) +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} +@(private) +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} + +/* +read_at returns n: 0, err: 0 on EOF +*/ +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m, e := pread(fd, b, offset) + if e == ERROR_EOF { + err = nil + break + } + if e != nil { + err = e + break + } + n += m + b = b[m:] + offset += i64(m) + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m := pwrite(fd, b, offset) or_return + n += m + b = b[m:] + offset += i64(m) + } + return +} + + + +// NOTE(bill): Uses startup to initialize it +stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) +stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) +stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) + + +@(require_results) +get_std_handle :: proc "contextless" (h: uint) -> Handle { + fd := win32.GetStdHandle(win32.DWORD(h)) + return Handle(fd) +} + + +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + return attribs != win32.INVALID_FILE_ATTRIBUTES +} + +@(require_results) +is_file :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 + } + return false +} + +@(require_results) +is_dir :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 + } + return false +} + +// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName +@private cwd_lock := win32.SRWLOCK{} // zero is initialized + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + win32.AcquireSRWLockExclusive(&cwd_lock) + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wstr := win32.utf8_to_wstring(path, context.temp_allocator) + + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = get_last_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} +change_directory :: set_current_directory + +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Mode is unused on Windows, but is needed on *nix + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.CreateDirectoryW(wpath, nil) { + err = get_last_error() + } + return +} + + +remove_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.RemoveDirectoryW(wpath) { + err = get_last_error() + } + return +} + + + +@(private, require_results) +is_abs :: proc(path: string) -> bool { + if len(path) > 0 && path[0] == '/' { + return true + } + when ODIN_OS == .Windows { + if len(path) > 2 { + switch path[0] { + case 'A'..='Z', 'a'..='z': + return path[1] == ':' && is_path_separator(path[2]) + } + } + } + return false +} + +@(private, require_results) +fix_long_path :: proc(path: string) -> string { + if len(path) < 248 { + return path + } + + if len(path) >= 2 && path[:2] == `\\` { + return path + } + if !is_abs(path) { + return path + } + + prefix :: `\\?` + + path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) + copy(path_buf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case is_path_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + return path + case: + path_buf[w] = '\\' + w += 1 + for ; r < n && !is_path_separator(path[r]); r += 1 { + path_buf[w] = path[r] + w += 1 + } + } + } + + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + return string(path_buf[:w]) +} + + +link :: proc(old_name, new_name: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + n := win32.utf8_to_wstring(fix_long_path(new_name)) + o := win32.utf8_to_wstring(fix_long_path(old_name)) + return Platform_Error(win32.CreateHardLinkW(n, o, nil)) +} + +unlink :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.DeleteFileW(wpath) { + err = get_last_error() + } + return +} + + + +rename :: proc(old_path, new_path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + from := win32.utf8_to_wstring(old_path, context.temp_allocator) + to := win32.utf8_to_wstring(new_path, context.temp_allocator) + + if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + err = get_last_error() + } + return +} + + +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + _= seek(fd, length, 0) or_return + ok := win32.SetEndOfFile(win32.HANDLE(fd)) + if !ok { + return get_last_error() + } + return nil +} + +truncate :: proc(path: string, length: i64) -> (err: Error) { + fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return + defer close(fd) + return ftruncate(fd, length) +} + + +remove :: proc(name: string) -> Error { + p := win32.utf8_to_wstring(fix_long_path(name)) + err, err1: win32.DWORD + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + if err == 0 { + return nil + } + if !win32.RemoveDirectoryW(p) { + err1 = win32.GetLastError() + } + if err1 == 0 { + return nil + } + + if err != err1 { + a := win32.GetFileAttributesW(p) + if a == ~u32(0) { + err = win32.GetLastError() + } else { + if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + err = err1 + } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { + if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { + err = 0 + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + } + } + } + } + + return Platform_Error(err) +} + + +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { + sa: win32.SECURITY_ATTRIBUTES + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { + err = get_last_error() + } + return +} 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/old/stat_unix.odin b/core/os/old/stat_unix.odin new file mode 100644 index 000000000..648987a07 --- /dev/null +++ b/core/os/old/stat_unix.odin @@ -0,0 +1,134 @@ +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +package os + +import "core:time" + +/* +For reference +------------- + +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + +Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + nlink: u64, // Number of hard links + mode: u32, // Mode of the file + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + _padding: i32, // 32 bits of padding + rdev: u64, // Device ID, if device + size: i64, // Size of the file, in bytes + block_size: i64, // Optimal bllocksize for I/O + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserve1, + _reserve2, + _reserve3: i64, +}; + +Time :: struct { + _nsec: i64, // zero is 1970-01-01 00:00:00 +} + +File_Info :: struct { + fullpath: string, + name: string, + size: i64, + mode: File_Mode, + is_dir: bool, + creation_time: time.Time, + modification_time: time.Time, + access_time: time.Time, +} +*/ + +@(private, require_results) +_make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time { + return time.Time{ + _nsec = i64(uft.nanoseconds) + i64(uft.seconds) * 1_000_000_000, + } +} + +@(private) +_fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { + fi.size = s.size + fi.mode = cast(File_Mode)s.mode + fi.is_dir = S_ISDIR(s.mode) + + // NOTE(laleksic, 2021-01-21): Not really creation time, but closest we can get (maybe better to leave it 0?) + fi.creation_time = _make_time_from_unix_file_time(s.status_change) + + fi.modification_time = _make_time_from_unix_file_time(s.modified) + fi.access_time = _make_time_from_unix_file_time(s.last_access) +} + + +@(private, require_results) +path_base :: proc(path: string) -> string { + is_separator :: proc(c: byte) -> bool { + return c == '/' + } + + if path == "" { + return "." + } + + path := path + for len(path) > 0 && is_separator(path[len(path)-1]) { + path = path[:len(path)-1] + } + + i := len(path)-1 + for i >= 0 && !is_separator(path[i]) { + i -= 1 + } + if i >= 0 { + path = path[i+1:] + } + if path == "" { + return "/" + } + return path +} + + +@(require_results) +lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { + context.allocator = allocator + + s := _lstat(name) or_return + _fill_file_info_from_stat(&fi, s) + fi.fullpath = absolute_path_from_relative(name) or_return + fi.name = path_base(fi.fullpath) + return +} + +@(require_results) +stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { + context.allocator = allocator + + s := _stat(name) or_return + _fill_file_info_from_stat(&fi, s) + fi.fullpath = absolute_path_from_relative(name) or_return + fi.name = path_base(fi.fullpath) + return +} + +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) { + context.allocator = allocator + + s := _fstat(fd) or_return + _fill_file_info_from_stat(&fi, s) + fi.fullpath = absolute_path_from_handle(fd) or_return + fi.name = path_base(fi.fullpath) + return +} 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/old/stream.odin b/core/os/old/stream.odin new file mode 100644 index 000000000..f4e9bcdde --- /dev/null +++ b/core/os/old/stream.odin @@ -0,0 +1,77 @@ +package os + +import "core:io" + +stream_from_handle :: proc(fd: Handle) -> io.Stream { + s: io.Stream + s.data = rawptr(uintptr(fd)) + s.procedure = _file_stream_proc + return s +} + + +@(private) +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + fd := Handle(uintptr(stream_data)) + n_int: int + os_err: Error + switch mode { + case .Close: + os_err = close(fd) + case .Flush: + os_err = flush(fd) + case .Read: + if len(p) == 0 { + return 0, nil + } + n_int, os_err = read(fd, p) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF + } + + case .Read_At: + if len(p) == 0 { + return 0, nil + } + n_int, os_err = read_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF + } + case .Write: + if len(p) == 0 { + return 0, nil + } + n_int, os_err = write(fd, p) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF + } + case .Write_At: + if len(p) == 0 { + return 0, nil + } + n_int, os_err = write_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF + } + case .Seek: + n, os_err = seek(fd, offset, int(whence)) + case .Size: + n, os_err = file_size(fd) + case .Destroy: + err = .Unsupported + case .Query: + return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) + } + + if err == nil && os_err != nil { + err = error_to_io_error(os_err) + } + if err != nil { + n = 0 + } + return +} diff --git a/core/os/os.odin b/core/os/os.odin deleted file mode 100644 index da7b0c151..000000000 --- a/core/os/os.odin +++ /dev/null @@ -1,266 +0,0 @@ -// Cross-platform `OS` interactions like file `I/O`. -package os - -import "base:intrinsics" -import "base:runtime" -import "core:io" -import "core:strconv" -import "core:strings" -import "core:unicode/utf8" - - -OS :: ODIN_OS -ARCH :: ODIN_ARCH -ENDIAN :: ODIN_ENDIAN - -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 - -write_string :: proc(fd: Handle, str: string) -> (int, Error) { - return write(fd, transmute([]byte)str) -} - -write_byte :: proc(fd: Handle, b: byte) -> (int, Error) { - return write(fd, []byte{b}) -} - -write_rune :: proc(fd: Handle, r: rune) -> (int, Error) { - if r < utf8.RUNE_SELF { - return write_byte(fd, byte(r)) - } - - b, n := utf8.encode_rune(r) - return write(fd, b[:n]) -} - -write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) { - wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { - n^ += m - if merr != nil { - err^ = merr - return true - } - return false - } - - if wrap(write_byte(f, '\''), &n, &err) { return } - - switch r { - case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } - case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } - case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } - case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } - case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } - case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } - case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } - case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } - case: - if r < 32 { - if wrap(write_string(f, "\\x"), &n, &err) { return } - b: [2]byte - s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) - switch len(s) { - case 0: if wrap(write_string(f, "00"), &n, &err) { return } - case 1: if wrap(write_rune(f, '0'), &n, &err) { return } - case 2: if wrap(write_string(f, s), &n, &err) { return } - } - } else { - if wrap(write_rune(f, r), &n, &err) { return } - } - } - _ = wrap(write_byte(f, '\''), &n, &err) - return -} - -read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) { - if len(buf) < min { - return 0, io.Error.Short_Buffer - } - nn := max(int) - for nn > 0 && n < min && err == nil { - nn, err = read(fd, buf[n:]) - n += nn - } - if n >= min { - err = nil - } - return -} - -read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) { - return read_at_least(fd, buf, len(buf)) -} - - -@(require_results) -file_size_from_path :: proc(path: string) -> i64 { - fd, err := open(path, O_RDONLY, 0) - if err != nil { - return -1 - } - defer close(fd) - - length: i64 - if length, err = file_size(fd); err != nil { - return -1 - } - return length -} - -@(require_results) -read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - err: Error - data, err = read_entire_file_from_filename_or_err(name, allocator, loc) - success = err == nil - return -} - -@(require_results) -read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - err: Error - data, err = read_entire_file_from_handle_or_err(fd, allocator, loc) - success = err == nil - return -} - -read_entire_file :: proc { - read_entire_file_from_filename, - read_entire_file_from_handle, -} - -@(require_results) -read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { - context.allocator = allocator - - fd := open(name, O_RDONLY, 0) or_return - defer close(fd) - - return read_entire_file_from_handle_or_err(fd, allocator, loc) -} - -@(require_results) -read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { - context.allocator = allocator - - length := file_size(fd) or_return - if length <= 0 { - return nil, nil - } - - data = make([]byte, int(length), allocator, loc) or_return - if data == nil { - return nil, nil - } - defer if err != nil { - delete(data, allocator) - } - - bytes_read := read_full(fd, data) or_return - data = data[:bytes_read] - return -} - -read_entire_file_or_err :: proc { - read_entire_file_from_filename_or_err, - read_entire_file_from_handle_or_err, -} - - -write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) { - return write_entire_file_or_err(name, data, truncate) == nil -} - -@(require_results) -write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error { - flags: int = O_WRONLY|O_CREATE - if truncate { - flags |= O_TRUNC - } - - mode: int = 0 - when OS == .Linux || OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd := open(name, flags, mode) or_return - defer close(fd) - - for n := 0; n < len(data); { - n += write(fd, data[n:]) or_return - } - return nil -} - -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { - return write(fd, ([^]byte)(data)[:len]) -} - -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { - return read(fd, ([^]byte)(data)[:len]) -} - -heap_allocator_proc :: runtime.heap_allocator_proc -heap_allocator :: runtime.heap_allocator - -heap_alloc :: runtime.heap_alloc -heap_resize :: runtime.heap_resize -heap_free :: runtime.heap_free - -@(require_results) -processor_core_count :: proc() -> int { - return _processor_core_count() -} - -// Always allocates for consistency. -replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) { - path := path - - sb: strings.Builder - strings.builder_init_none(&sb, allocator) - for len(path) > 0 { - switch path[0] { - case '%': // Windows - when ODIN_OS == .Windows { - for r, i in path[1:] { - if r == '%' { - env_key := path[1:i+1] - env_val := get_env(env_key, context.temp_allocator) - strings.write_string(&sb, env_val) - path = path[i+1:] // % is part of key, so skip 1 character extra - } - } - } else { - strings.write_rune(&sb, rune(path[0])) - } - - case '$': // Posix - when ODIN_OS != .Windows { - env_key := "" - dollar_loop: for r, i in path[1:] { - switch r { - case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident - case: - env_key = path[1:i+1] - break dollar_loop - } - } - if len(env_key) > 0 { - env_val := get_env(env_key, context.temp_allocator) - strings.write_string(&sb, env_val) - path = path[len(env_key):] - } - - } else { - strings.write_rune(&sb, rune(path[0])) - } - - case: - strings.write_rune(&sb, rune(path[0])) - } - - path = path[1:] - } - return strings.to_string(sb) -} \ No newline at end of file diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin deleted file mode 100644 index 36a7d72be..000000000 --- a/core/os/os2/allocators.odin +++ /dev/null @@ -1,74 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -@(require_results) -file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() -} - -@(private="file") -MAX_TEMP_ARENA_COUNT :: 2 -@(private="file") -MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1 -@(private="file", thread_local) -global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena - -@(fini, private) -temp_allocator_fini :: proc "contextless" () { - for &arena in global_default_temp_allocator_arenas { - runtime.arena_destroy(&arena) - } - global_default_temp_allocator_arenas = {} -} - -Temp_Allocator :: struct { - using arena: ^runtime.Arena, - using allocator: runtime.Allocator, - tmp: runtime.Arena_Temp, - loc: runtime.Source_Code_Location, -} - -TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) { - runtime.arena_temp_end(temp.tmp, temp.loc) -} - -@(deferred_out=TEMP_ALLOCATOR_GUARD_END) -TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator { - assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!") - good_arena: ^runtime.Arena - for i in 0.. (runtime.Arena_Temp) { - return temp_allocator_begin(tmp.arena) -} -@(private="file") -_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) { - temp_allocator_end(tmp) -} - -@(init, private) -init_thread_local_cleaner :: proc "contextless" () { - runtime.add_thread_local_cleaner(temp_allocator_fini) -} diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin deleted file mode 100644 index f63754273..000000000 --- a/core/os/os2/dir.odin +++ /dev/null @@ -1,262 +0,0 @@ -package os2 - -import "base:runtime" -import "core:slice" -import "core:strings" - -read_dir :: read_directory - -/* - 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`. -*/ -@(require_results) -read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { - if f == nil { - return nil, .Invalid_File - } - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - it := read_directory_iterator_create(f) - defer _read_directory_iterator_destroy(&it) - - dfi := make([dynamic]File_Info, 0, size, temp_allocator) - defer if err != nil { - for fi in dfi { - file_info_delete(fi, allocator) - } - } - - for fi, index in read_directory_iterator(&it) { - if n > 0 && index == n { - break - } - - _ = read_directory_iterator_error(&it) or_break - - append(&dfi, file_info_clone(fi, allocator) or_return) - } - - _ = read_directory_iterator_error(&it) or_return - - return slice.clone(dfi[:], allocator) -} - - -/* - Reads the file `f` (assuming it is a directory) and returns all of the unsorted directory entries. -*/ -@(require_results) -read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { - return read_directory(f, -1, allocator) -} - -/* - 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`. -*/ -@(require_results) -read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { - f := open(path) or_return - defer close(f) - return read_directory(f, n, allocator) -} - -/* - Reads the named directory by path (assuming it is a directory) and returns all of the unsorted directory entries. -*/ -@(require_results) -read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { - return read_directory_by_path(path, -1, allocator) -} - - - -Read_Directory_Iterator :: struct { - f: ^File, - err: struct { - err: Error, - path: [dynamic]byte, - }, - index: int, - impl: Read_Directory_Iterator_Impl, -} - -/* -Creates a directory iterator with the given directory. - -For an example on how to use the iterator, see `read_directory_iterator`. -*/ -read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator) { - read_directory_iterator_init(&it, f) - return -} - -/* -Initialize a directory iterator with the given directory. - -This procedure may be called on an existing iterator to reuse it for another directory. - -For an example on how to use the iterator, see `read_directory_iterator`. -*/ -read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { - it.err.err = nil - it.err.path.allocator = file_allocator() - clear(&it.err.path) - - it.f = f - it.index = 0 - - _read_directory_iterator_init(it, f) -} - -/* -Destroys a directory iterator. -*/ -read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { - if it == nil { - return - } - - delete(it.err.path) - - _read_directory_iterator_destroy(it) -} - -/* -Retrieve the last error that happened during iteration. -*/ -@(require_results) -read_directory_iterator_error :: proc(it: ^Read_Directory_Iterator) -> (path: string, err: Error) { - return string(it.err.path[:]), it.err.err -} - -@(private) -read_directory_iterator_set_error :: proc(it: ^Read_Directory_Iterator, path: string, err: Error) { - if err == nil { - return - } - - resize(&it.err.path, len(path)) - copy(it.err.path[:], path) - - it.err.err = err -} - -/* -Returns the next file info entry for the iterator's directory. - -The given `File_Info` is reused in subsequent calls so a copy (`file_info_clone`) has to be made to -extend its lifetime. - -Example: - package main - - import "core:fmt" - import os "core:os/os2" - - main :: proc() { - f, oerr := os.open("core") - ensure(oerr == nil) - defer os.close(f) - - it := os.read_directory_iterator_create(f) - defer os.read_directory_iterator_destroy(&it) - - for info in os.read_directory_iterator(&it) { - // Optionally break on the first error: - // Supports not doing this, and keeping it going with remaining items. - // _ = os.read_directory_iterator_error(&it) or_break - - // Handle error as we go: - // Again, no need to do this as it will keep going with remaining items. - if path, err := os.read_directory_iterator_error(&it); err != nil { - fmt.eprintfln("failed reading %s: %s", path, err) - continue - } - - // Or, do not handle errors during iteration, and just check the error at the end. - - - fmt.printfln("%#v", info) - } - - // Handle error if one happened during iteration at the end: - if path, err := os.read_directory_iterator_error(&it); err != nil { - fmt.eprintfln("read directory failed at %s: %s", path, err) - } - } -*/ -@(require_results) -read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - if it.f == nil { - return - } - - if it.index == 0 && it.err.err != nil { - return - } - - return _read_directory_iterator(it) -} - -// Recursively copies a directory to `dst` from `src` -copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { - when #defined(_copy_directory_all_native) { - return _copy_directory_all_native(dst, src, dst_perm) - } else { - return _copy_directory_all(dst, src, dst_perm) - } -} - -@(private) -_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { - err := make_directory(dst, dst_perm) - if err != nil && err != .Exist { - return err - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - abs_src := get_absolute_path(src, temp_allocator) or_return - abs_dst := get_absolute_path(dst, temp_allocator) or_return - - dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return - - w: Walker - walker_init_path(&w, src) - defer walker_destroy(&w) - - for info in walker_walk(&w) { - _ = walker_error(&w) or_break - - rel := strings.trim_prefix(info.fullpath, abs_src) - - non_zero_resize(&dst_buf, 0) - reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return - append(&dst_buf, abs_dst) - append(&dst_buf, Path_Separator_String) - append(&dst_buf, rel) - - if info.type == .Directory { - err = make_directory(string(dst_buf[:]), dst_perm) - if err != nil && err != .Exist { - return err - } - } else { - copy_file(string(dst_buf[:]), info.fullpath) or_return - } - } - - _ = walker_error(&w) or_return - - return nil -} diff --git a/core/os/os2/dir_js.odin b/core/os/os2/dir_js.odin deleted file mode 100644 index d8f7c6202..000000000 --- a/core/os/os2/dir_js.odin +++ /dev/null @@ -1,24 +0,0 @@ -#+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/dir_linux.odin b/core/os/os2/dir_linux.odin deleted file mode 100644 index 34346c02f..000000000 --- a/core/os/os2/dir_linux.odin +++ /dev/null @@ -1,120 +0,0 @@ -#+private -package os2 - -import "core:sys/linux" - -Read_Directory_Iterator_Impl :: struct { - prev_fi: File_Info, - dirent_backing: []u8, - dirent_buflen: int, - dirent_off: int, -} - -@(require_results) -_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - scan_entries :: proc(it: ^Read_Directory_Iterator, dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) { - for d in linux.dirent_iterate_buf(entries, offset) { - file_name = linux.dirent_name(d) - if file_name == "." || file_name == ".." { - continue - } - - file_name_cstr := cstring(raw_data(file_name)) - entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH}) - if errno == .NONE { - return entry_fd, file_name - } else { - read_directory_iterator_set_error(it, file_name, _get_platform_error(errno)) - } - } - - return -1, "" - } - - index = it.index - it.index += 1 - - dfd := linux.Fd(_fd(it.f)) - - entries := it.impl.dirent_backing[:it.impl.dirent_buflen] - entry_fd, file_name := scan_entries(it, dfd, entries, &it.impl.dirent_off) - - for entry_fd == -1 { - if len(it.impl.dirent_backing) == 0 { - it.impl.dirent_backing = make([]u8, 512, file_allocator()) - } - - loop: for { - buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:]) - #partial switch errno { - case .EINVAL: - delete(it.impl.dirent_backing, file_allocator()) - n := len(it.impl.dirent_backing) * 2 - it.impl.dirent_backing = make([]u8, n, file_allocator()) - continue - case .NONE: - if buflen == 0 { - return - } - it.impl.dirent_off = 0 - it.impl.dirent_buflen = buflen - entries = it.impl.dirent_backing[:buflen] - break loop - case: - read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno)) - return - } - } - - entry_fd, file_name = scan_entries(it, dfd, entries, &it.impl.dirent_off) - } - defer linux.close(entry_fd) - - // PERF: reuse the fullpath string like on posix and wasi. - file_info_delete(it.impl.prev_fi, file_allocator()) - - err: Error - fi, err = _fstat_internal(entry_fd, file_allocator()) - it.impl.prev_fi = fi - - if err != nil { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - path, _ := _get_full_path(entry_fd, temp_allocator) - read_directory_iterator_set_error(it, path, err) - } - - ok = true - return -} - -_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { - // NOTE: Allow calling `init` to target a new directory with the same iterator. - it.impl.dirent_buflen = 0 - it.impl.dirent_off = 0 - - if f == nil || f.impl == nil { - read_directory_iterator_set_error(it, "", .Invalid_File) - return - } - - stat: linux.Stat - errno := linux.fstat(linux.Fd(fd(f)), &stat) - if errno != .NONE { - read_directory_iterator_set_error(it, name(f), _get_platform_error(errno)) - return - } - - if (stat.mode & linux.S_IFMT) != linux.S_IFDIR { - read_directory_iterator_set_error(it, name(f), .Invalid_Dir) - return - } -} - -_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { - if it == nil { - return - } - - delete(it.impl.dirent_backing, file_allocator()) - file_info_delete(it.impl.prev_fi, file_allocator()) -} diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin deleted file mode 100644 index d9fa16f8d..000000000 --- a/core/os/os2/dir_posix.odin +++ /dev/null @@ -1,104 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "core:sys/posix" - -Read_Directory_Iterator_Impl :: struct { - dir: posix.DIR, - fullpath: [dynamic]byte, -} - -@(require_results) -_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - fimpl := (^File_Impl)(it.f.impl) - - index = it.index - it.index += 1 - - for { - posix.set_errno(nil) - entry := posix.readdir(it.impl.dir) - if entry == nil { - if errno := posix.errno(); errno != nil { - read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno)) - } - return - } - - cname := cstring(raw_data(entry.d_name[:])) - if cname == "." || cname == ".." { - continue - } - sname := string(cname) - - n := len(fimpl.name)+1 - if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil { - read_directory_iterator_set_error(it, sname, err) - ok = true - return - } - copy(it.impl.fullpath[n:], sname) - - stat: posix.stat_t - if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { - read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error()) - ok = true - return - } - - fi = internal_stat(stat, string(it.impl.fullpath[:])) - ok = true - return - } -} - -_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { - if f == nil || f.impl == nil { - read_directory_iterator_set_error(it, "", .Invalid_File) - return - } - - impl := (^File_Impl)(f.impl) - - // NOTE: Allow calling `init` to target a new directory with the same iterator. - it.impl.fullpath.allocator = file_allocator() - clear(&it.impl.fullpath) - if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil { - read_directory_iterator_set_error(it, name(f), err) - return - } - - append(&it.impl.fullpath, impl.name) - append(&it.impl.fullpath, "/") - - // `fdopendir` consumes the file descriptor so we need to `dup` it. - dupfd := posix.dup(impl.fd) - if dupfd == -1 { - read_directory_iterator_set_error(it, name(f), _get_platform_error()) - return - } - defer if it.err.err != nil { posix.close(dupfd) } - - // NOTE: Allow calling `init` to target a new directory with the same iterator. - if it.impl.dir != nil { - posix.closedir(it.impl.dir) - } - - it.impl.dir = posix.fdopendir(dupfd) - if it.impl.dir == nil { - read_directory_iterator_set_error(it, name(f), _get_platform_error()) - return - } - - return -} - -_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { - if it.impl.dir == nil { - return - } - - posix.closedir(it.impl.dir) - delete(it.impl.fullpath) -} diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/os2/dir_posix_darwin.odin deleted file mode 100644 index 3cae50d25..000000000 --- a/core/os/os2/dir_posix_darwin.odin +++ /dev/null @@ -1,17 +0,0 @@ -#+private -package os2 - -import "core:sys/darwin" - -_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - csrc := clone_to_cstring(src, temp_allocator) or_return - cdst := clone_to_cstring(dst, temp_allocator) or_return - - if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 { - err = _get_platform_error() - } - - return -} diff --git a/core/os/os2/dir_walker.odin b/core/os/os2/dir_walker.odin deleted file mode 100644 index ba5342cf8..000000000 --- a/core/os/os2/dir_walker.odin +++ /dev/null @@ -1,230 +0,0 @@ -package os2 - -import "core:container/queue" - -/* -A recursive directory walker. - -Note that none of the fields should be accessed directly. -*/ -Walker :: struct { - todo: queue.Queue(string), - skip_dir: bool, - err: struct { - path: [dynamic]byte, - err: Error, - }, - iter: Read_Directory_Iterator, -} - -walker_init_path :: proc(w: ^Walker, path: string) { - cloned_path, err := clone_string(path, file_allocator()) - if err != nil { - walker_set_error(w, path, err) - return - } - - walker_clear(w) - - if _, err = queue.push(&w.todo, cloned_path); err != nil { - walker_set_error(w, cloned_path, err) - return - } -} - -walker_init_file :: proc(w: ^Walker, f: ^File) { - handle, err := clone(f) - if err != nil { - path, _ := clone_string(name(f), file_allocator()) - walker_set_error(w, path, err) - return - } - - walker_clear(w) - - read_directory_iterator_init(&w.iter, handle) -} - -/* -Initializes a walker, either using a path or a file pointer to a directory the walker will start at. - -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 :: proc { - walker_init_path, - walker_init_file, -} - -@(require_results) -walker_create_path :: proc(path: string) -> (w: Walker) { - walker_init_path(&w, path) - return -} - -@(require_results) -walker_create_file :: proc(f: ^File) -> (w: Walker) { - walker_init_file(&w, f) - return -} - -/* -Creates a walker, either using a path or a file pointer to a directory the walker will start at. - -For an example on how to use the walker, see `walker_walk`. -*/ -walker_create :: proc { - walker_create_path, - walker_create_file, -} - -/* -Returns the last error that occurred during the walker's operations. - -Can be called while iterating, or only at the end to check if anything failed. -*/ -@(require_results) -walker_error :: proc(w: ^Walker) -> (path: string, err: Error) { - return string(w.err.path[:]), w.err.err -} - -@(private) -walker_set_error :: proc(w: ^Walker, path: string, err: Error) { - if err == nil { - return - } - - resize(&w.err.path, len(path)) - copy(w.err.path[:], path) - - w.err.err = err -} - -@(private) -walker_clear :: proc(w: ^Walker) { - w.iter.f = nil - w.skip_dir = false - - w.err.path.allocator = file_allocator() - clear(&w.err.path) - - w.todo.data.allocator = file_allocator() - for path in queue.pop_front_safe(&w.todo) { - delete(path, file_allocator()) - } -} - -walker_destroy :: proc(w: ^Walker) { - walker_clear(w) - queue.destroy(&w.todo) - delete(w.err.path) - read_directory_iterator_destroy(&w.iter) -} - -// Marks the current directory to be skipped (not entered into). -walker_skip_dir :: proc(w: ^Walker) { - w.skip_dir = true -} - -/* -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) - } - } -*/ -@(require_results) -walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) { - if w.skip_dir { - w.skip_dir = false - if skip, sok := queue.pop_back_safe(&w.todo); sok { - delete(skip, file_allocator()) - } - } - - if w.iter.f == nil { - if queue.len(w.todo) == 0 { - return - } - - next := queue.pop_front(&w.todo) - - handle, err := open(next) - if err != nil { - walker_set_error(w, next, err) - return {}, true - } - - read_directory_iterator_init(&w.iter, handle) - - delete(next, file_allocator()) - } - - info, _, iter_ok := read_directory_iterator(&w.iter) - - if path, err := read_directory_iterator_error(&w.iter); err != nil { - walker_set_error(w, path, err) - } - - if !iter_ok { - close(w.iter.f) - w.iter.f = nil - return walker_walk(w) - } - - if info.type == .Directory { - path, err := clone_string(info.fullpath, file_allocator()) - if err != nil { - walker_set_error(w, "", err) - return - } - - _, err = queue.push_back(&w.todo, path) - if err != nil { - walker_set_error(w, path, err) - return - } - } - - return info, iter_ok -} \ No newline at end of file diff --git a/core/os/os2/dir_wasi.odin b/core/os/os2/dir_wasi.odin deleted file mode 100644 index 9804f07fd..000000000 --- a/core/os/os2/dir_wasi.odin +++ /dev/null @@ -1,123 +0,0 @@ -#+private -package os2 - -import "core:slice" -import "base:intrinsics" -import "core:sys/wasm/wasi" - -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) { - fimpl := (^File_Impl)(it.f.impl) - - buf := it.impl.buf[it.impl.off:] - - index = it.index - it.index += 1 - - for { - if len(buf) < size_of(wasi.dirent_t) { - return - } - - entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf))) - buf = buf[size_of(wasi.dirent_t):] - - assert(len(buf) < int(entry.d_namlen)) - - name := string(buf[:entry.d_namlen]) - buf = buf[entry.d_namlen:] - it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen) - - if name == "." || name == ".." { - continue - } - - n := len(fimpl.name)+1 - if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil { - read_directory_iterator_set_error(it, name, alloc_err) - ok = true - return - } - copy(it.impl.fullpath[n:], name) - - stat, err := wasi.path_filestat_get(__fd(it.f), {}, name) - if err != nil { - // Can't stat, fill what we have from dirent. - stat = { - ino = entry.d_ino, - filetype = entry.d_type, - } - read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error(err)) - } - - fi = internal_stat(stat, string(it.impl.fullpath[:])) - ok = true - return - } -} - -_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { - // NOTE: Allow calling `init` to target a new directory with the same iterator. - it.impl.off = 0 - - if f == nil || f.impl == nil { - read_directory_iterator_set_error(it, "", .Invalid_File) - return - } - - impl := (^File_Impl)(f.impl) - - buf: [dynamic]byte - // NOTE: Allow calling `init` to target a new directory with the same iterator. - if it.impl.buf != nil { - buf = slice.into_dynamic(it.impl.buf) - } - buf.allocator = file_allocator() - - defer if it.err.err != nil { delete(buf) } - - for { - if err := non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2); err != nil { - read_directory_iterator_set_error(it, name(f), err) - return - } - - n, err := wasi.fd_readdir(__fd(f), buf[:], 0) - if err != nil { - read_directory_iterator_set_error(it, name(f), _get_platform_error(err)) - return - } - - if n < len(buf) { - non_zero_resize(&buf, n) - break - } - - assert(n == len(buf)) - } - it.impl.buf = buf[:] - - // NOTE: Allow calling `init` to target a new directory with the same iterator. - it.impl.fullpath.allocator = file_allocator() - clear(&it.impl.fullpath) - if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil { - read_directory_iterator_set_error(it, name(f), err) - return - } - - append(&it.impl.fullpath, impl.name) - append(&it.impl.fullpath, "/") - - return -} - -_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { - delete(it.impl.buf, file_allocator()) - delete(it.impl.fullpath) -} 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/doc.odin b/core/os/os2/doc.odin deleted file mode 100644 index 2ebdd0912..000000000 --- a/core/os/os2/doc.odin +++ /dev/null @@ -1,6 +0,0 @@ -// Package os 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. -// Features not generally available appear in the system-specific packages under core:sys/*. -package os2 diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin deleted file mode 100644 index 310d45af1..000000000 --- a/core/os/os2/env.odin +++ /dev/null @@ -1,122 +0,0 @@ -package os2 - -import "base:runtime" -import "core:strings" - -// `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: runtime.Allocator) -> string { - value, _ := lookup_env(key, allocator) - return value -} - -// `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: this version takes a backing buffer for the string value -@(require_results) -get_env_buf :: proc(buf: []u8, key: string) -> string { - value, _ := lookup_env(buf, key) - return value -} - -get_env :: proc{get_env_alloc, get_env_buf} - -// `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: runtime.Allocator) -> (value: string, found: bool) { - return _lookup_env_alloc(key, allocator) -} - -// 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) { - return _lookup_env_buf(buf, key) -} - -lookup_env :: proc{lookup_env_alloc, lookup_env_buf} - -// set_env sets the value of the environment variable named by the key -// Returns Error on failure -set_env :: proc(key, value: string) -> Error { - return _set_env(key, value) -} - -// unset_env unsets a single environment variable -// Returns true on success, false on failure -unset_env :: proc(key: string) -> bool { - return _unset_env(key) -} - -clear_env :: proc() { - _clear_env() -} - - -// 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: runtime.Allocator) -> ([]string, Error) { - return _environ(allocator) -} - -// Always allocates for consistency. -replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) { - path := path - - sb: strings.Builder - strings.builder_init_none(&sb, allocator) - - for len(path) > 0 { - switch path[0] { - case '%': // Windows - when ODIN_OS == .Windows { - for r, i in path[1:] { - if r == '%' { - env_key := path[1:i+1] - env_val := get_env(env_key, context.temp_allocator) - strings.write_string(&sb, env_val) - path = path[i+1:] // % is part of key, so skip 1 character extra - } - } - } else { - strings.write_rune(&sb, rune(path[0])) - } - - case '$': // Posix - when ODIN_OS != .Windows { - env_key := "" - dollar_loop: for r, i in path[1:] { - switch r { - case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident - case: - env_key = path[1:i+1] - break dollar_loop - } - } - if len(env_key) > 0 { - env_val := get_env(env_key, context.temp_allocator) - strings.write_string(&sb, env_val) - path = path[len(env_key):] - } - - } else { - strings.write_rune(&sb, rune(path[0])) - } - - case: - strings.write_rune(&sb, rune(path[0])) - } - - path = path[1:] - } - return strings.to_string(sb) -} \ No newline at end of file diff --git a/core/os/os2/env_js.odin b/core/os/os2/env_js.odin deleted file mode 100644 index c1d94ba4a..000000000 --- a/core/os/os2/env_js.odin +++ /dev/null @@ -1,42 +0,0 @@ -#+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/env_linux.odin b/core/os/os2/env_linux.odin deleted file mode 100644 index 7855fbfed..000000000 --- a/core/os/os2/env_linux.odin +++ /dev/null @@ -1,369 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import "base:intrinsics" - -import "core:sync" -import "core:slice" -import "core:strings" -import "core:sys/linux" -import "core:sys/posix" - -_ :: sync -_ :: slice -_ :: linux -_ :: posix - -when ODIN_NO_CRT { - // TODO: Override the libc environment functions' weak linkage to - // allow us to interact with 3rd party code that DOES link - // to libc. Otherwise, our environment can be out of sync. - - NOT_FOUND :: -1 - - // the environment is a 0 delimited list of = strings - _env: [dynamic]string - - _env_mutex: sync.Recursive_Mutex - - // We need to be able to figure out if the environment variable - // is contained in the original environment or not. This also - // serves as a flag to determine if we have built _env. - _org_env_begin: uintptr // atomic - _org_env_end: uintptr // guarded by _env_mutex - - // Returns value + index location into _env - // or -1 if not found - _lookup :: proc(key: string) -> (value: string, idx: int) { - sync.guard(&_env_mutex) - - for entry, i in _env { - if k, v := _kv_from_entry(entry); k == key { - return v, i - } - } - return "", -1 - } - - _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - - if v, idx := _lookup(key); idx != -1 { - found = true - value, _ = clone_string(v, allocator) - } - return - } - - _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - - if v, idx := _lookup(key); idx != -1 { - if len(buf) >= len(v) { - copy(buf, v) - return string(buf[:len(v)]), nil - } - return "", .Buffer_Full - } - return "", .Env_Var_Not_Found - } - - _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - - _set_env :: proc(key, v_new: string) -> Error { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - // all key values are stored as "key=value\x00" - kv_size := len(key) + len(v_new) + 2 - if v_curr, idx := _lookup(key); idx != NOT_FOUND { - if v_curr == v_new { - return nil - } - - unordered_remove(&_env, idx) - - if !_is_in_org_env(v_curr) { - // We allocated this key-value. Possibly resize and - // overwrite the value only. Otherwise, treat as if it - // wasn't in the environment in the first place. - k_addr, v_addr := _kv_addr_from_val(v_curr, key) - if len(v_new) > len(v_curr) { - k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) - if k_addr == nil { - return .Out_Of_Memory - } - v_addr = &k_addr[len(key) + 1] - } - intrinsics.mem_copy_non_overlapping(v_addr, raw_data(v_new), len(v_new)) - v_addr[len(v_new)] = 0 - - append(&_env, string(k_addr[:kv_size])) - return nil - } - } - - k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) - if k_addr == nil { - return .Out_Of_Memory - } - intrinsics.mem_copy_non_overlapping(k_addr, raw_data(key), len(key)) - k_addr[len(key)] = '=' - - val_slice := k_addr[len(key) + 1:] - intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) - val_slice[len(v_new)] = 0 - - append(&_env, string(k_addr[:kv_size - 1])) - return nil - } - - _unset_env :: proc(key: string) -> bool { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - v: string - i: int - if v, i = _lookup(key); i == -1 { - return true - } - - unordered_remove(&_env, i) - - if _is_in_org_env(v) { - return true - } - - // if we got this far, the environment variable - // existed AND was allocated by us. - k_addr, _ := _kv_addr_from_val(v, key) - runtime.heap_free(k_addr) - return true - } - - _clear_env :: proc() { - sync.guard(&_env_mutex) - - for kv in _env { - if !_is_in_org_env(kv) { - runtime.heap_free(raw_data(kv)) - } - } - clear(&_env) - - // nothing resides in the original environment either - intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release) - _org_env_end = ~uintptr(0) - } - - _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - _build_env() - } - sync.guard(&_env_mutex) - - env := make([dynamic]string, 0, len(_env), allocator) or_return - defer if err != nil { - for e in env { - delete(e, allocator) - } - delete(env) - } - - for entry in _env { - s := clone_string(entry, allocator) or_return - append(&env, s) - } - environ = env[:] - return - } - - // The entire environment is stored as 0 terminated strings, - // so there is no need to clone/free individual variables - export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { - // The environment has not been modified, so we can just - // send the original environment - org_env := _get_original_env() - n: int - for ; org_env[n] != nil; n += 1 {} - return slice.clone(org_env[:n + 1], allocator) - } - sync.guard(&_env_mutex) - - // NOTE: already terminated by nil pointer via + 1 - env := make([]cstring, len(_env) + 1, allocator) - - for entry, i in _env { - env[i] = cstring(raw_data(entry)) - } - return env - } - - _build_env :: proc() { - sync.guard(&_env_mutex) - if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 { - return - } - - _env = make(type_of(_env), runtime.heap_allocator()) - cstring_env := _get_original_env() - intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release) - for i := 0; cstring_env[i] != nil; i += 1 { - bytes := ([^]u8)(cstring_env[i]) - n := len(cstring_env[i]) - _org_env_end = uintptr(&bytes[n]) - append(&_env, string(bytes[:n])) - } - } - - _get_original_env :: #force_inline proc() -> [^]cstring { - // essentially &argv[argc] which should be a nil pointer! - #no_bounds_check env: [^]cstring = &runtime.args__[len(runtime.args__)] - assert(env[0] == nil) - return &env[1] - } - - _kv_from_entry :: #force_inline proc(entry: string) -> (k, v: string) { - eq_idx := strings.index_byte(entry, '=') - if eq_idx == -1 { - return entry, "" - } - return entry[:eq_idx], entry[eq_idx + 1:] - } - - _kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^]u8) { - v_addr := raw_data(val) - k_addr := ([^]u8)(&v_addr[-(len(key) + 1)]) - return k_addr, v_addr - } - - _is_in_org_env :: #force_inline proc(env_data: string) -> bool { - addr := uintptr(raw_data(env_data)) - return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end - } - -} else { - - _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if key == "" { - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - ckey := strings.clone_to_cstring(key, temp_allocator) - cval := posix.getenv(ckey) - if cval == nil { - return - } - - found = true - value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? - - return - } - - _lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { - if key == "" { - return - } - - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - cval := posix.getenv(cstring(raw_data(buf))) - if cval == nil { - return - } - - if value = string(cval); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } - } - - _lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - - _set_env :: proc(key, value: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - ckey := strings.clone_to_cstring(key, temp_allocator) or_return - cval := strings.clone_to_cstring(value, temp_allocator) or_return - - if posix.setenv(ckey, cval, true) != nil { - posix_errno := posix.errno() - linux_errno := cast(linux.Errno)(cast(int)posix_errno) - err = _get_platform_error(linux_errno) - } - return - } - - _unset_env :: proc(key: string) -> (ok: bool) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - ckey := strings.clone_to_cstring(key, temp_allocator) - - ok = posix.unsetenv(ckey) == .OK - return - } - - // NOTE(laytan): clearing the env is weird, why would you ever do that? - - _clear_env :: proc() { - for entry := posix.environ[0]; entry != nil; entry = posix.environ[0] { - key := strings.truncate_to_byte(string(entry), '=') - _unset_env(key) - } - } - - _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - n := 0 - for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} - - r := make([dynamic]string, 0, n, allocator) or_return - defer if err != nil { - for e in r { - delete(e, allocator) - } - delete(r) - } - - for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { - append(&r, strings.clone(string(entry), allocator) or_return) - } - - environ = r[:] - return - } - - - - export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { - env := make([dynamic]cstring, allocator) - for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { - append(&env, entry) - } - append(&env, nil) - return env[:] - } -} diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin deleted file mode 100644 index 72a1daf18..000000000 --- a/core/os/os2/env_posix.odin +++ /dev/null @@ -1,110 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -import "core:strings" -import "core:sys/posix" - -_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if key == "" { - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - ckey := strings.clone_to_cstring(key, temp_allocator) - cval := posix.getenv(ckey) - if cval == nil { - return - } - - found = true - value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? - - return -} - -_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { - if key == "" { - return - } - - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - cval := posix.getenv(cstring(raw_data(buf))) - if cval == nil { - return - } - - if value = string(cval); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} - -_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - -_set_env :: proc(key, value: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - ckey := strings.clone_to_cstring(key, temp_allocator) or_return - cval := strings.clone_to_cstring(value, temp_allocator) or_return - - if posix.setenv(ckey, cval, true) != nil { - err = _get_platform_error_from_errno() - } - return -} - -_unset_env :: proc(key: string) -> (ok: bool) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - ckey := strings.clone_to_cstring(key, temp_allocator) - - ok = posix.unsetenv(ckey) == .OK - return -} - -// NOTE(laytan): clearing the env is weird, why would you ever do that? - -_clear_env :: proc() { - for entry := posix.environ[0]; entry != nil; entry = posix.environ[0] { - key := strings.truncate_to_byte(string(entry), '=') - _unset_env(key) - } -} - -_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - n := 0 - for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} - - r := make([dynamic]string, 0, n, allocator) or_return - defer if err != nil { - for e in r { - delete(e, allocator) - } - delete(r) - } - - for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { - append(&r, strings.clone(string(entry), allocator) or_return) - } - - environ = r[:] - return -} - - diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin deleted file mode 100644 index cb40667cf..000000000 --- a/core/os/os2/env_wasi.odin +++ /dev/null @@ -1,188 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:strings" -import "core:sync" -import "core:sys/wasm/wasi" - -g_env: map[string]string -g_env_buf: []byte -g_env_mutex: sync.RW_Mutex -g_env_error: Error -g_env_built: bool - -build_env :: proc() -> (err: Error) { - if g_env_built || g_env_error != nil { - return g_env_error - } - - sync.guard(&g_env_mutex) - - if g_env_built || g_env_error != nil { - return g_env_error - } - - defer if err != nil { - g_env_error = err - } - - num_envs, size_of_envs, _err := wasi.environ_sizes_get() - if _err != nil { - return _get_platform_error(_err) - } - - g_env = make(map[string]string, num_envs, file_allocator()) or_return - defer if err != nil { delete(g_env) } - - g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return - defer if err != nil { delete(g_env_buf, file_allocator()) } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - envs := make([]cstring, num_envs, temp_allocator) or_return - - _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) - if _err != nil { - return _get_platform_error(_err) - } - - for env in envs { - key, _, value := strings.partition(string(env), "=") - g_env[key] = value - } - - g_env_built = true - return -} - -delete_string_if_not_original :: proc(str: string) { - start := uintptr(raw_data(g_env_buf)) - end := start + uintptr(len(g_env_buf)) - ptr := uintptr(raw_data(str)) - if ptr < start || ptr > end { - delete(str, file_allocator()) - } -} - -@(require_results) -_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if err := build_env(); err != nil { - return - } - - sync.shared_guard(&g_env_mutex) - - value = g_env[key] or_return - value, _ = clone_string(value, allocator) - return -} - -_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { - if key == "" { - return - } - - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - sync.shared_guard(&g_env_mutex) - - val, ok := g_env[key] - - if !ok { - return "", .Env_Var_Not_Found - } else { - if len(val) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, val) - return string(buf[:len(val)]), nil - } - } -} -_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} - -@(require_results) -_set_env :: proc(key, value: string) -> (err: Error) { - build_env() or_return - - sync.guard(&g_env_mutex) - - defer if err != nil { - delete_key(&g_env, key) - } - - key_ptr, value_ptr, just_inserted := map_entry(&g_env, key) or_return - - if just_inserted { - key_ptr^ = clone_string(key, file_allocator()) or_return - defer if err != nil { - delete(key_ptr^, file_allocator()) - } - value_ptr^ = clone_string(value, file_allocator()) or_return - return - } - - delete_string_if_not_original(value_ptr^) - - value_ptr^ = clone_string(value, file_allocator()) or_return - return -} - -@(require_results) -_unset_env :: proc(key: string) -> bool { - if err := build_env(); err != nil { - return false - } - - sync.guard(&g_env_mutex) - - dkey, dval := delete_key(&g_env, key) - delete_string_if_not_original(dkey) - delete_string_if_not_original(dval) - return true -} - -_clear_env :: proc() { - sync.guard(&g_env_mutex) - - for k, v in g_env { - delete_string_if_not_original(k) - delete_string_if_not_original(v) - } - - delete(g_env_buf, file_allocator()) - g_env_buf = {} - - clear(&g_env) - - g_env_built = true -} - -@(require_results) -_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - build_env() or_return - - sync.shared_guard(&g_env_mutex) - - envs := make([dynamic]string, 0, len(g_env), allocator) or_return - defer if err != nil { - for env in envs { - delete(env, allocator) - } - delete(envs) - } - - for k, v in g_env { - append(&envs, concatenate({k, "=", v}, allocator) or_return) - } - - environ = envs[:] - return -} 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/errors_js.odin b/core/os/os2/errors_js.odin deleted file mode 100644 index c92d36736..000000000 --- a/core/os/os2/errors_js.odin +++ /dev/null @@ -1,13 +0,0 @@ -#+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/errors_linux.odin b/core/os/os2/errors_linux.odin deleted file mode 100644 index a7556c306..000000000 --- a/core/os/os2/errors_linux.odin +++ /dev/null @@ -1,177 +0,0 @@ -#+private -package os2 - -import "core:sys/linux" - -_Platform_Error :: linux.Errno - -@(rodata) -_errno_strings := [linux.Errno]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", -} - - -_get_platform_error :: proc(errno: linux.Errno) -> Error { - #partial switch errno { - case .NONE: - return nil - case .EPERM: - return .Permission_Denied - case .EEXIST: - return .Exist - case .ENOENT: - return .Not_Exist - case .ETIMEDOUT: - return .Timeout - case .EPIPE: - return .Broken_Pipe - case .EBADF: - return .Invalid_File - case .ENOMEM: - return .Out_Of_Memory - case .ENOSYS: - return .Unsupported - } - - return Platform_Error(i32(errno)) -} - -_error_string :: proc(errno: i32) -> string { - if errno >= 0 && errno <= i32(max(linux.Errno)) { - return _errno_strings[linux.Errno(errno)] - } - return "Unknown Error" -} diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin deleted file mode 100644 index 8a9ca07df..000000000 --- a/core/os/os2/errors_posix.odin +++ /dev/null @@ -1,43 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "core:sys/posix" - -_Platform_Error :: posix.Errno - -_error_string :: proc(errno: i32) -> string { - return string(posix.strerror(posix.Errno(errno))) -} - -_get_platform_error_from_errno :: proc() -> Error { - return _get_platform_error_existing(posix.errno()) -} - -_get_platform_error_existing :: proc(errno: posix.Errno) -> Error { - #partial switch errno { - case .EPERM: - return .Permission_Denied - case .EEXIST: - return .Exist - case .ENOENT: - return .Not_Exist - case .ETIMEDOUT: - return .Timeout - case .EPIPE: - return .Broken_Pipe - case .EBADF: - return .Invalid_File - case .ENOMEM: - return .Out_Of_Memory - case .ENOSYS: - return .Unsupported - case: - return Platform_Error(errno) - } -} - -_get_platform_error :: proc{ - _get_platform_error_existing, - _get_platform_error_from_errno, -} diff --git a/core/os/os2/errors_wasi.odin b/core/os/os2/errors_wasi.odin deleted file mode 100644 index b88e5b81e..000000000 --- a/core/os/os2/errors_wasi.odin +++ /dev/null @@ -1,47 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:slice" -import "core:sys/wasm/wasi" - -_Platform_Error :: wasi.errno_t - -_error_string :: proc(errno: i32) -> string { - e := wasi.errno_t(errno) - if e == .NONE { - return "" - } - - err := runtime.Type_Info_Enum_Value(e) - - ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum) - if idx, ok := slice.binary_search(ti.values, err); ok { - return ti.names[idx] - } - return "" -} - -_get_platform_error :: proc(errno: wasi.errno_t) -> Error { - #partial switch errno { - case .PERM: - return .Permission_Denied - case .EXIST: - return .Exist - case .NOENT: - return .Not_Exist - case .TIMEDOUT: - return .Timeout - case .PIPE: - return .Broken_Pipe - case .BADF: - return .Invalid_File - case .NOMEM: - return .Out_Of_Memory - case .NOSYS: - return .Unsupported - case: - return Platform_Error(errno) - } -} diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin deleted file mode 100644 index 404560f98..000000000 --- a/core/os/os2/errors_windows.odin +++ /dev/null @@ -1,78 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import "core:slice" -import win32 "core:sys/windows" - -_Platform_Error :: win32.System_Error - -_error_string :: proc(errno: i32) -> string { - e := win32.DWORD(errno) - if e == 0 { - return "" - } - - err := runtime.Type_Info_Enum_Value(e) - - ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) - if idx, ok := slice.binary_search(ti.values, err); ok { - return ti.names[idx] - } - return "" -} - -_get_platform_error :: proc() -> Error { - err := win32.GetLastError() - if err == 0 { - return nil - } - switch err { - case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: - return .Permission_Denied - - case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: - return .Exist - - case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: - return .Not_Exist - - case win32.ERROR_NO_DATA: - return .Closed - - case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: - return .Timeout - - case win32.ERROR_NOT_SUPPORTED: - return .Unsupported - - case win32.ERROR_HANDLE_EOF: - return .EOF - - case win32.ERROR_INVALID_HANDLE: - return .Invalid_File - - case win32.ERROR_NEGATIVE_SEEK: - return .Invalid_Offset - - case win32.ERROR_BROKEN_PIPE: - return .Broken_Pipe - - case - win32.ERROR_BAD_ARGUMENTS, - win32.ERROR_INVALID_PARAMETER, - win32.ERROR_NOT_ENOUGH_MEMORY, - win32.ERROR_NO_MORE_FILES, - win32.ERROR_LOCK_VIOLATION, - win32.ERROR_CALL_NOT_IMPLEMENTED, - win32.ERROR_INSUFFICIENT_BUFFER, - win32.ERROR_INVALID_NAME, - win32.ERROR_LOCK_FAILED, - win32.ERROR_ENVVAR_NOT_FOUND, - win32.ERROR_OPERATION_ABORTED, - win32.ERROR_IO_PENDING, - win32.ERROR_NO_UNICODE_TRANSLATION: - // fallthrough - } - return Platform_Error(err) -} diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin deleted file mode 100644 index bf7ebaeb5..000000000 --- a/core/os/os2/file.odin +++ /dev/null @@ -1,570 +0,0 @@ -package os2 - -import "core:io" -import "core:time" -import "base:runtime" - -/* - Type representing a file handle. - - This struct represents an OS-specific file-handle, which can be one of - the following: - - File - - Directory - - Pipe - - Named pipe - - Block Device - - Character device - - Symlink - - Socket - - See `File_Type` enum for more information on file types. -*/ -File :: struct { - impl: rawptr, - stream: File_Stream, -} - -/* - Type representing the type of a file handle. - - **Note(windows)**: Socket handles can not be distinguished from - files, as they are just a normal file handle that is being treated by - a special driver. Windows also makes no distinction between block and - character devices. -*/ -File_Type :: enum { - // The type of a file could not be determined for the current platform. - Undetermined, - // Represents a regular file. - Regular, - // Represents a directory. - Directory, - // Represents a symbolic link. - Symlink, - // Represents a named pipe (FIFO). - Named_Pipe, - // Represents a socket. - // **Note(windows)**: Not returned on windows - Socket, - // Represents a block device. - // **Note(windows)**: On windows represents all devices. - Block_Device, - // Represents a character device. - // **Note(windows)**: Not returned on windows - Character_Device, -} - -// Represents the file flags for a file handle -File_Flags :: distinct bit_set[File_Flag; uint] -File_Flag :: enum { - Read, - Write, - Append, - Create, - Excl, - Sync, - Trunc, - Sparse, - Inheritable, - Non_Blocking, - Unbuffered_IO, -} - -O_RDONLY :: File_Flags{.Read} -O_WRONLY :: File_Flags{.Write} -O_RDWR :: File_Flags{.Read, .Write} -O_APPEND :: File_Flags{.Append} -O_CREATE :: File_Flags{.Create} -O_EXCL :: File_Flags{.Excl} -O_SYNC :: File_Flags{.Sync} -O_TRUNC :: File_Flags{.Trunc} -O_SPARSE :: File_Flags{.Sparse} - -/* - If specified, the file handle is inherited upon the creation of a child - process. By default all handles are created non-inheritable. - - **Note**: The standard file handles (stderr, stdout and stdin) are always - initialized as inheritable. -*/ -O_INHERITABLE :: File_Flags{.Inheritable} - -Permissions :: distinct bit_set[Permission_Flag; u32] -Permission_Flag :: enum u32 { - Execute_Other = 0, - Write_Other = 1, - Read_Other = 2, - - Execute_Group = 3, - Write_Group = 4, - Read_Group = 5, - - Execute_User = 6, - Write_User = 7, - Read_User = 8, -} - -Permissions_Execute_All :: Permissions{.Execute_User, .Execute_Group, .Execute_Other} -Permissions_Write_All :: Permissions{.Write_User, .Write_Group, .Write_Other} -Permissions_Read_All :: Permissions{.Read_User, .Read_Group, .Read_Other} - -Permissions_Read_Write_All :: Permissions_Read_All + Permissions_Write_All - -Permissions_All :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All - -Permissions_Default_File :: Permissions_Read_All + Permissions_Write_All -Permissions_Default_Directory :: Permissions_Read_All + Permissions_Write_All + Permissions_Execute_All -Permissions_Default :: Permissions_Default_Directory - -perm :: proc{ - perm_number, -} - -/* - `perm_number` converts an integer value `perm` to the bit set `Permissions` -*/ -@(require_results) -perm_number :: proc "contextless" (perm: int) -> Permissions { - return transmute(Permissions)u32(perm & 0o777) -} - - - -// `stdin` is an open file pointing to the standard input file stream -stdin: ^File = nil // OS-Specific - -// `stdout` is an open file pointing to the standard output file stream -stdout: ^File = nil // OS-Specific - -// `stderr` is an open file pointing to the standard error file stream -stderr: ^File = nil // OS-Specific - -/* - `create` creates or truncates a named file `name`. - If the file already exists, it is truncated. - If the file does not exist, it is created with the `Permissions_Default_File` permissions. - If successful, a `^File` is return which can be used for I/O. - And error is returned if any is encountered. -*/ -@(require_results) -create :: proc(name: string) -> (^File, Error) { - return open(name, {.Read, .Write, .Create, .Trunc}, Permissions_Default_File) -} - -/* - `open` is a generalized open call, which defaults to opening for reading. - If the file does not exist, and the `{.Create}` flag is passed, it is created with the permissions `perm`, - and please note that the containing directory must exist otherwise and an error will be returned. - If successful, a `^File` is return which can be used for I/O. - And error is returned if any is encountered. -*/ -@(require_results) -open :: proc(name: string, flags := File_Flags{.Read}, perm := Permissions_Default) -> (^File, Error) { - return _open(name, flags, perm) -} - -// @(require_results) -// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { -// if buffer_size == 0 { -// return _open(name, flags, perm) -// } -// return _open_buffered(name, buffer_size, flags, perm) -// } - -/* - `new_file` returns a new `^File` with the given file descriptor `handle` and `name`. - The return value will only be `nil` IF the `handle` is not a valid file descriptor. -*/ -@(require_results) -new_file :: proc(handle: uintptr, name: string) -> ^File { - file, err := _new_file(handle, name, file_allocator()) - if err != nil { - panic(error_string(err)) - } - return file -} - -/* - `clone` returns a new `^File` based on the passed file `f` with the same underlying file descriptor. -*/ -@(require_results) -clone :: proc(f: ^File) -> (^File, Error) { - return _clone(f) -} - -/* - `fd` returns the file descriptor of the file `f` passed. If the file is not valid, an invalid handle will be returned. -*/ -@(require_results) -fd :: proc(f: ^File) -> uintptr { - return _fd(f) -} - -/* - `name` returns the name of the file. The lifetime of this string lasts as long as the file handle itself. -*/ -@(require_results) -name :: proc(f: ^File) -> string { - return _name(f) -} - -/* - Close a file and its stream. - - Any further use of the file or its stream should be considered to be in the - same class of bugs as a use-after-free. -*/ -close :: proc(f: ^File) -> Error { - if f != nil { - if f.stream.procedure == nil { - return .Unsupported - } - _, err := f.stream.procedure(f, .Close, nil, 0, nil, runtime.nil_allocator()) - return err - } - return nil -} - -/* - seek sets the offsets for the next read or write on a file to a specified `offset`, - according to what `whence` is set. - `.Start` is relative to the origin of the file. - `.Current` is relative to the current offset. - `.End` is relative to the end. - It returns the new offset and an error, if any is encountered. - Prefer `read_at` or `write_at` if the offset does not want to be changed. - -*/ -seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - return f.stream.procedure(f, .Seek, nil, offset, whence, runtime.nil_allocator()) - } - return 0, .Invalid_File -} - -/* - `read` reads up to len(p) bytes from the file `f`, and then stores them in `p`. - It returns the number of bytes read and an error, if any is encountered. - At the end of a file, it returns `0, io.EOF`. -*/ -read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - n64: i64 - n64, err = f.stream.procedure(f, .Read, p, 0, nil, runtime.nil_allocator()) - return int(n64), err - } - return 0, .Invalid_File -} - -/* - `read_at` reads up to len(p) bytes from the file `f` at the byte offset `offset`, and then stores them in `p`. - It returns the number of bytes read and an error, if any is encountered. - `read_at` always returns a non-nil error when `n < len(p)`. - At the end of a file, the error is `io.EOF`. -*/ -read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - n64: i64 - n64, err = f.stream.procedure(f, .Read_At, p, offset, nil, runtime.nil_allocator()) - return int(n64), err - } - return 0, .Invalid_File -} - -/* - `write` writes `len(p)` bytes from `p` to the file `f`. It returns the number of bytes written to - and an error, if any is encountered. - `write` returns a non-nil error when `n != len(p)`. -*/ -write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - n64: i64 - n64, err = f.stream.procedure(f, .Write, p, 0, nil, runtime.nil_allocator()) - return int(n64), err - } - return 0, .Invalid_File -} - -/* - `write_at` writes `len(p)` bytes from `p` to the file `f` starting at byte offset `offset`. - It returns the number of bytes written to and an error, if any is encountered. - `write_at` returns a non-nil error when `n != len(p)`. -*/ -write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - n64: i64 - n64, err = f.stream.procedure(f, .Write_At, p, offset, nil, runtime.nil_allocator()) - return int(n64), err - } - return 0, .Invalid_File -} - -/* - `file_size` returns the length of the file `f` in bytes and an error, if any is encountered. -*/ -file_size :: proc(f: ^File) -> (n: i64, err: Error) { - if f != nil { - if f.stream.procedure == nil { - return 0, .Unsupported - } - n, err = f.stream.procedure(f, .Size, nil, 0, nil, runtime.nil_allocator()) - if err == .Unsupported { - n = 0 - curr := seek(f, 0, .Current) or_return - end := seek(f, 0, .End) or_return - seek(f, curr, .Start) or_return - n = end - } - return - } - return 0, .Invalid_File -} - -/* - `flush` flushes a file `f` -*/ -flush :: proc(f: ^File) -> Error { - if f != nil { - if f.stream.procedure == nil { - return .Unsupported - } - _, err := f.stream.procedure(f, .Flush, nil, 0, nil, runtime.nil_allocator()) - return err - } - return nil -} - -/* - `sync` commits the current contents of the file `f` to stable storage. - This usually means flushing the file system's in-memory copy to disk. -*/ -sync :: proc(f: ^File) -> Error { - return _sync(f) -} - -/* - `truncate` changes the size of the file `f` to `size` in bytes. - This can be used to shorten or lengthen a file. - It does not change the "offset" of the file. -*/ -truncate :: proc(f: ^File, size: i64) -> Error { - return _truncate(f, size) -} - -/* - `remove` removes a named file or (empty) directory. -*/ -remove :: proc(name: string) -> Error { - return _remove(name) -} - -/* - `rename` renames (moves) `old_path` to `new_path`. -*/ -rename :: proc(old_path, new_path: string) -> Error { - return _rename(old_path, new_path) -} - -/* - `link` creates a `new_name` as a hard link to the `old_name` file. -*/ -link :: proc(old_name, new_name: string) -> Error { - return _link(old_name, new_name) -} - -/* - `symlink` creates a `new_name` as a symbolic link to the `old_name` file. -*/ -symlink :: proc(old_name, new_name: string) -> Error { - return _symlink(old_name, new_name) -} - -/* - `read_link` returns the destinction of the named symbolic link `name`. -*/ -read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) { - return _read_link(name,allocator) -} - - -chdir :: change_directory - -/* - Changes the current working directory to the named directory. -*/ -change_directory :: proc(name: string) -> Error { - return _chdir(name) -} - -chmod :: change_mode - -/* - Changes the mode/permissions of the named file to `mode`. - If the file is a symbolic link, it changes the mode of the link's target. - - On Windows, only `{.Write_User}` of `mode` is used, and controls whether or not - the file has a read-only attribute. Use `{.Read_User}` for a read-only file and - `{.Read_User, .Write_User}` for a readable & writable file. -*/ -change_mode :: proc(name: string, mode: Permissions) -> Error { - return _chmod(name, mode) -} - -chown :: change_owner - -/* - Changes the numeric `uid` and `gid` of a named file. If the file is a symbolic link, - it changes the `uid` and `gid` of the link's target. - - On Windows, it always returns an error. -*/ -change_owner :: proc(name: string, uid, gid: int) -> Error { - return _chown(name, uid, gid) -} - -fchdir :: fchange_directory - -/* - Changes the current working directory to the file, which must be a directory. -*/ -fchange_directory :: proc(f: ^File) -> Error { - return _fchdir(f) -} - -fchmod :: fchange_mode - -/* - Changes the current `mode` permissions of the file `f`. -*/ -fchange_mode :: proc(f: ^File, mode: Permissions) -> Error { - return _fchmod(f, mode) -} - -fchown :: fchange_owner - -/* - Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link, - it changes the `uid` and `gid` of the link's target. - - On Windows, it always returns an error. -*/ -fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { - return _fchown(f, uid, gid) -} - - -lchown :: change_owner_do_not_follow_links - -/* - Changes the numeric `uid` and `gid` of the file `f`. If the file is a symbolic link, - it changes the `uid` and `gid` of the lin itself. - - On Windows, it always returns an error. -*/ -change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { - return _lchown(name, uid, gid) -} - -chtimes :: change_times - -/* - Changes the access `atime` and modification `mtime` times of a named file. -*/ -change_times :: proc(name: string, atime, mtime: time.Time) -> Error { - return _chtimes(name, atime, mtime) -} - -fchtimes :: fchange_times - -/* - Changes the access `atime` and modification `mtime` times of the file `f`. -*/ -fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { - return _fchtimes(f, atime, mtime) -} - -/* - `exists` returns whether or not a named file exists. -*/ -@(require_results) -exists :: proc(path: string) -> bool { - return _exists(path) -} - -/* - `is_file` returns whether or not the type of a named file is a `File_Type.Regular` file. -*/ -@(require_results) -is_file :: proc(path: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - fi, err := stat(path, temp_allocator) - if err != nil { - return false - } - return fi.type == .Regular -} - -is_dir :: is_directory - -/* - Returns whether or not the type of a named file is a `File_Type.Directory` file. -*/ -@(require_results) -is_directory :: proc(path: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - fi, err := stat(path, temp_allocator) - if err != nil { - return false - } - return fi.type == .Directory -} - -/* - `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) - } else { - return _copy_file(dst_path, src_path) - } -} - -@(private) -_copy_file :: proc(dst_path, src_path: string) -> Error { - src := open(src_path) or_return - defer close(src) - - info := fstat(src, file_allocator()) or_return - defer file_info_delete(info, file_allocator()) - if info.type == .Directory { - return .Invalid_File - } - - dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & Permissions_All) or_return - defer close(dst) - - _, 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 deleted file mode 100644 index 91ee7f02e..000000000 --- a/core/os/os2/file_js.odin +++ /dev/null @@ -1,110 +0,0 @@ -#+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: Permissions) -> (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 -} - -_is_tty :: proc "contextless" (f: ^File) -> bool { - return true -} - -_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: Permissions) -> Error { - return .Unsupported -} - -_chmod :: proc(name: string, mode: Permissions) -> 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/file_linux.odin b/core/os/os2/file_linux.odin deleted file mode 100644 index f5f2ebdd7..000000000 --- a/core/os/os2/file_linux.odin +++ /dev/null @@ -1,560 +0,0 @@ -#+private -package os2 - -import "base:runtime" -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, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -MAX_RW :: 1 << 30 - -File_Impl :: struct { - file: File, - name: string, - fd: linux.Fd, - allocator: runtime.Allocator, - - buffer: []byte, - rw_mutex: sync.RW_Mutex, // read write calls - p_mutex: sync.Mutex, // pread pwrite calls -} - -_stdin := File{ - stream = { - procedure = _file_stream_proc, - }, -} -_stdout := File{ - stream = { - procedure = _file_stream_proc, - }, -} -_stderr := File{ - stream = { - procedure = _file_stream_proc, - }, -} - -@init -_standard_stream_init :: proc "contextless" () { - new_std :: proc "contextless" (impl: ^File_Impl, fd: linux.Fd, name: string) -> ^File { - impl.file.impl = impl - impl.fd = linux.Fd(fd) - impl.allocator = runtime.nil_allocator() - impl.name = name - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - return &impl.file - } - - @(static) files: [3]File_Impl - stdin = new_std(&files[0], 0, "/proc/self/fd/0") - stdout = new_std(&files[1], 1, "/proc/self/fd/1") - stderr = new_std(&files[2], 2, "/proc/self/fd/2") -} - -_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - - // Just default to using O_NOCTTY because needing to open a controlling - // terminal would be incredibly rare. This has no effect on files while - // allowing us to open serial devices. - sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} - when size_of(rawptr) == 4 { - sys_flags += {.LARGEFILE} - } - switch flags & (O_RDONLY|O_WRONLY|O_RDWR) { - case O_RDONLY: - case O_WRONLY: sys_flags += {.WRONLY} - case O_RDWR: sys_flags += {.RDWR} - } - if .Append in flags { sys_flags += {.APPEND} } - if .Create in flags { sys_flags += {.CREAT} } - if .Excl in flags { sys_flags += {.EXCL} } - if .Sync in flags { sys_flags += {.DSYNC} } - if .Trunc in flags { sys_flags += {.TRUNC} } - if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } - if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - - fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)transmute(u32)perm) - if errno != .NONE { - return nil, _get_platform_error(errno) - } - - return _new_file(uintptr(fd), name, file_allocator()) -} - -_new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { - impl := new(File_Impl, allocator) or_return - defer if err != nil { - free(impl, allocator) - } - impl.file.impl = impl - impl.fd = linux.Fd(fd) - impl.allocator = allocator - impl.name = _get_full_path(impl.fd, impl.allocator) or_return - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - return &impl.file, nil -} - -_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { - if f == nil || f.impl == nil { - return - } - - fd := (^File_Impl)(f.impl).fd - - clonefd, errno := linux.dup(fd) - if errno != nil { - err = _get_platform_error(errno) - return - } - defer if err != nil { linux.close(clonefd) } - - return _new_file(uintptr(clonefd), "", file_allocator()) -} - - -@(require_results) -_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) { - assert(buffer_size > 0) - f, err = _open(name, flags, perm) - if f != nil && err == nil { - impl := (^File_Impl)(f.impl) - impl.buffer = make([]byte, buffer_size, file_allocator()) - f.stream.procedure = _file_stream_buffered_proc - } - return -} - -_destroy :: proc(f: ^File_Impl) -> Error { - if f == nil { - return nil - } - a := f.allocator - err0 := delete(f.name, a) - err1 := delete(f.buffer, a) - err2 := free(f, a) - err0 or_return - err1 or_return - err2 or_return - return nil -} - - -_close :: proc(f: ^File_Impl) -> Error { - if f == nil{ - return nil - } - errno := linux.close(f.fd) - if errno == .EBADF { // avoid possible double free - return _get_platform_error(errno) - } - _destroy(f) - return _get_platform_error(errno) -} - -_fd :: proc(f: ^File) -> uintptr { - if f == nil || f.impl == nil { - return ~uintptr(0) - } - impl := (^File_Impl)(f.impl) - 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 "" -} - -_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - // We have to handle this here, because Linux returns EINVAL for both - // invalid offsets and invalid whences. - switch whence { - case .Start, .Current, .End: - break - case: - return 0, .Invalid_Whence - } - n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) - #partial switch errno { - case .EINVAL: - return 0, .Invalid_Offset - case .NONE: - return n, nil - case: - return 0, _get_platform_error(errno) - } -} - -_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { - if len(p) <= 0 { - return 0, nil - } - - n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)]) - if errno != .NONE { - return 0, _get_platform_error(errno) - } - return i64(n), io.Error.EOF if n == 0 else nil -} - -_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { - if len(p) <= 0 { - return 0, nil - } - if offset < 0 { - return 0, .Invalid_Offset - } - n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset) - if errno != .NONE { - return 0, _get_platform_error(errno) - } - if n == 0 { - return 0, .EOF - } - return i64(n), nil -} - -_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) { - p := p - for len(p) > 0 { - n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)]) - if errno != .NONE { - err = _get_platform_error(errno) - return - } - - p = p[n:] - nt += i64(n) - } - - return -} - -_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - p := p - offset := offset - for len(p) > 0 { - n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset) - if errno != .NONE { - err = _get_platform_error(errno) - return - } - - p = p[n:] - nt += i64(n) - offset += i64(n) - } - - return -} - -@(no_sanitize_memory) -_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { - // TODO: Identify 0-sized "pseudo" files and return No_Size. This would - // eliminate the need for the _read_entire_pseudo_file procs. - s: linux.Stat = --- - errno := linux.fstat(f.fd, &s) - if errno != .NONE { - return 0, _get_platform_error(errno) - } - - if s.mode & linux.S_IFMT == linux.S_IFREG { - return i64(s.size), nil - } - return 0, .No_Size -} - -_sync :: proc(f: ^File) -> Error { - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.fsync(impl.fd)) -} - -_flush :: proc(f: ^File_Impl) -> Error { - return _get_platform_error(linux.fsync(f.fd)) -} - -_truncate :: proc(f: ^File, size: i64) -> Error { - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.ftruncate(impl.fd, size)) -} - -_remove :: proc(name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - - if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { - linux.close(fd) - return _get_platform_error(linux.rmdir(name_cstr)) - } - - return _get_platform_error(linux.unlink(name_cstr)) -} - -_rename :: proc(old_name, new_name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return - new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return - - return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) -} - -_link :: proc(old_name, new_name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return - new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return - - return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) -} - -_symlink :: proc(old_name, new_name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return - new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return - return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) -} - -_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) { - bufsz : uint = 256 - buf := make([]byte, bufsz, allocator) - for { - sz, errno := linux.readlink(name_cstr, buf[:]) - if errno != .NONE { - delete(buf, allocator) - return "", _get_platform_error(errno) - } else if sz == int(bufsz) { - bufsz *= 2 - delete(buf, allocator) - buf = make([]byte, bufsz, allocator) - } else { - return string(buf[:sz]), nil - } - } -} - -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _read_link_cstr(name_cstr, allocator) -} - -_chdir :: proc(name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _get_platform_error(linux.chdir(name_cstr)) -} - -_fchdir :: proc(f: ^File) -> Error { - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.fchdir(impl.fd)) -} - -_chmod :: proc(name: string, mode: Permissions) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)transmute(u32)mode)) -} - -_fchmod :: proc(f: ^File, mode: Permissions) -> Error { - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)transmute(u32)mode)) -} - -// NOTE: will throw error without super user priviledges -_chown :: proc(name: string, uid, gid: int) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) -} - -// NOTE: will throw error without super user priviledges -_lchown :: proc(name: string, uid, gid: int) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) -} - -// NOTE: will throw error without super user priviledges -_fchown :: proc(f: ^File, uid, gid: int) -> Error { - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) -} - -_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - times := [2]linux.Time_Spec { - { - uint(atime._nsec) / uint(time.Second), - uint(atime._nsec) % uint(time.Second), - }, - { - uint(mtime._nsec) / uint(time.Second), - uint(mtime._nsec) % uint(time.Second), - }, - } - return _get_platform_error(linux.utimensat(linux.AT_FDCWD, name_cstr, ×[0], nil)) -} - -_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - times := [2]linux.Time_Spec { - { - uint(atime._nsec) / uint(time.Second), - uint(atime._nsec) % uint(time.Second), - }, - { - uint(mtime._nsec) / uint(time.Second), - uint(mtime._nsec) % uint(time.Second), - }, - } - impl := (^File_Impl)(f.impl) - return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) -} - -_exists :: proc(name: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - name_cstr, _ := clone_to_cstring(name, temp_allocator) - return linux.access(name_cstr, linux.F_OK) == .NONE -} - -/* For reading Linux system files that stat to size 0 */ -_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } - -_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - return _read_entire_pseudo_file_cstring(name_cstr, allocator) -} - -_read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Allocator) -> ([]u8, Error) { - fd, errno := linux.open(name, {}) - if errno != .NONE { - return nil, _get_platform_error(errno) - } - defer linux.close(fd) - - BUF_SIZE_STEP :: 128 - contents := make([dynamic]u8, 0, BUF_SIZE_STEP, allocator) - - n: int - i: int - for { - resize(&contents, i + BUF_SIZE_STEP) - n, errno = linux.read(fd, contents[i:i+BUF_SIZE_STEP]) - if errno != .NONE { - delete(contents) - return nil, _get_platform_error(errno) - } - if n < BUF_SIZE_STEP { - break - } - i += BUF_SIZE_STEP - } - - resize(&contents, i + n) - return contents[:], nil -} - -@(private="package") -_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) - switch mode { - case .Read: - n, err = _read(f, p) - return - case .Read_At: - n, err = _read_at(f, p, offset) - return - case .Write: - n, err = _write(f, p) - return - case .Write_At: - n, err = _write_at(f, p, offset) - return - case .Seek: - n, err = _seek(f, offset, whence) - return - case .Size: - n, err = _file_size(f) - return - case .Flush: - err = _flush(f) - return - case .Close, .Destroy: - err = _close(f) - return - 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 - } - return 0, .Unsupported -} - - -@(private="package") -_file_stream_buffered_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) - switch mode { - case .Read: - n, err = _read(f, p) - return - case .Read_At: - n, err = _read_at(f, p, offset) - return - case .Write: - n, err = _write(f, p) - return - case .Write_At: - n, err = _write_at(f, p, offset) - return - case .Seek: - n, err = _seek(f, offset, whence) - return - case .Size: - n, err = _file_size(f) - return - case .Flush: - err = _flush(f) - return - case .Close, .Destroy: - err = _close(f) - return - 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 - } - return 0, .Unsupported -} - diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin deleted file mode 100644 index ef53bf116..000000000 --- a/core/os/os2/file_posix.odin +++ /dev/null @@ -1,514 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -import "core:io" -import "core:c" -import "core:time" -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, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -MAX_RW :: 1 << 30 - -File_Impl :: struct { - file: File, - name: string, - cname: cstring, - fd: posix.FD, - allocator: runtime.Allocator, -} - -@(init) -init_std_files :: proc "contextless" () { - new_std :: proc "contextless" (impl: ^File_Impl, fd: posix.FD, name: cstring) -> ^File { - impl.file.impl = impl - impl.fd = fd - impl.allocator = runtime.nil_allocator() - impl.cname = name - impl.name = string(name) - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - return &impl.file - } - - @(static) files: [3]File_Impl - stdin = new_std(&files[0], posix.STDIN_FILENO, "/dev/stdin") - stdout = new_std(&files[1], posix.STDOUT_FILENO, "/dev/stdout") - stderr = new_std(&files[2], posix.STDERR_FILENO, "/dev/stderr") -} - -_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC} - - if .Write in flags { - if .Read in flags { - sys_flags += {.RDWR} - } else { - sys_flags += {.WRONLY} - } - } - - if .Append in flags { sys_flags += {.APPEND} } - if .Create in flags { sys_flags += {.CREAT} } - if .Excl in flags { sys_flags += {.EXCL} } - if .Sync in flags { sys_flags += {.DSYNC} } - if .Trunc in flags { sys_flags += {.TRUNC} } - if .Non_Blocking in flags { sys_flags += {.NONBLOCK} } - if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - - fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(transmute(u32)perm)) - if fd < 0 { - err = _get_platform_error() - return - } - - return _new_file(uintptr(fd), name, file_allocator()) -} - -_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { - if name == "" { - err = .Invalid_Path - return - } else if handle == ~uintptr(0) { - err = .Invalid_File - return - } - - crname := _posix_absolute_path(posix.FD(handle), name, allocator) or_return - rname := string(crname) - - f = __new_file(posix.FD(handle), allocator) - impl := (^File_Impl)(f.impl) - impl.name = rname - impl.cname = crname - - return f, nil -} - -__new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File { - impl := new(File_Impl, allocator) - impl.file.impl = impl - impl.fd = posix.FD(handle) - impl.allocator = allocator - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - return &impl.file -} - -_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { - if f == nil || f.impl == nil { - err = .Invalid_Pointer - return - } - - impl := (^File_Impl)(f.impl) - - fd := posix.dup(impl.fd) - if fd <= 0 { - err = _get_platform_error() - return - } - defer if err != nil { posix.close(fd) } - - clone = __new_file(fd, file_allocator()) - clone_impl := (^File_Impl)(clone.impl) - clone_impl.cname = clone_to_cstring(impl.name, file_allocator()) or_return - clone_impl.name = string(clone_impl.cname) - - return -} - -_close :: proc(f: ^File_Impl) -> (err: Error) { - if f == nil { return nil } - - if posix.close(f.fd) != .OK { - err = _get_platform_error() - } - - allocator := f.allocator - - delete(f.cname, allocator) - free(f, allocator) - return -} - -_fd :: proc(f: ^File) -> uintptr { - return uintptr(__fd(f)) -} - -__fd :: proc(f: ^File) -> posix.FD { - if f != nil && f.impl != nil { - return (^File_Impl)(f.impl).fd - } - return -1 -} - -_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) -} - -_name :: proc(f: ^File) -> string { - if f != nil && f.impl != nil { - return (^File_Impl)(f.impl).name - } - return "" -} - -_sync :: proc(f: ^File) -> Error { - if posix.fsync(__fd(f)) != .OK { - return _get_platform_error() - } - return nil -} - -_truncate :: proc(f: ^File, size: i64) -> Error { - if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK { - return _get_platform_error() - } - return nil -} - -_remove :: proc(name: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.remove(cname) != 0 { - return _get_platform_error() - } - return nil -} - -_rename :: proc(old_path, new_path: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cold := clone_to_cstring(old_path, temp_allocator) or_return - cnew := clone_to_cstring(new_path, temp_allocator) or_return - if posix.rename(cold, cnew) != 0 { - return _get_platform_error() - } - return nil -} - -_link :: proc(old_name, new_name: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cold := clone_to_cstring(old_name, temp_allocator) or_return - cnew := clone_to_cstring(new_name, temp_allocator) or_return - if posix.link(cold, cnew) != .OK { - return _get_platform_error() - } - return nil -} - -_symlink :: proc(old_name, new_name: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cold := clone_to_cstring(old_name, temp_allocator) or_return - cnew := clone_to_cstring(new_name, temp_allocator) or_return - if posix.symlink(cold, cnew) != .OK { - return _get_platform_error() - } - return nil -} - -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - cname := clone_to_cstring(name, temp_allocator) or_return - - buf: [dynamic]byte - buf.allocator = allocator - defer if err != nil { delete(buf) } - - // Loop this because the file might've grown between lstat() and readlink(). - for { - stat: posix.stat_t - if posix.lstat(cname, &stat) != .OK { - err = _get_platform_error() - return - } - - bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX) - - if bufsiz == len(buf) { - bufsiz *= 2 - } - - // Overflow. - if bufsiz <= 0 { - err = Platform_Error(posix.Errno.E2BIG) - return - } - - resize(&buf, bufsiz) or_return - - size := posix.readlink(cname, raw_data(buf), uint(bufsiz)) - if size < 0 { - err = _get_platform_error() - return - } - - // File has probably grown between lstat() and readlink(). - if size == bufsiz { - continue - } - - s = string(buf[:size]) - return - } -} - -_chdir :: proc(name: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.chdir(cname) != .OK { - return _get_platform_error() - } - return nil -} - -_fchdir :: proc(f: ^File) -> Error { - if posix.fchdir(__fd(f)) != .OK { - return _get_platform_error() - } - return nil -} - -_fchmod :: proc(f: ^File, mode: Permissions) -> Error { - if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK { - return _get_platform_error() - } - return nil -} - -_chmod :: proc(name: string, mode: Permissions) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(transmute(u32)mode)) != .OK { - return _get_platform_error() - } - return nil -} - -_fchown :: proc(f: ^File, uid, gid: int) -> Error { - if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK { - return _get_platform_error() - } - return nil -} - -_chown :: proc(name: string, uid, gid: int) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { - return _get_platform_error() - } - return nil -} - -_lchown :: proc(name: string, uid, gid: int) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { - return _get_platform_error() - } - return nil -} - -_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) { - times := [2]posix.timeval{ - { - tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ - tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */ - }, - { - tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ - tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */ - }, - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - - if posix.utimes(cname, ×) != .OK { - return _get_platform_error() - } - return nil -} - -_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - times := [2]posix.timespec{ - { - tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ - tv_nsec = c.long(atime._nsec%1e9), /* nanoseconds */ - }, - { - tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ - tv_nsec = c.long(mtime._nsec%1e9), /* nanoseconds */ - }, - } - - if posix.futimens(__fd(f), ×) != .OK { - return _get_platform_error() - } - return nil -} - -_exists :: proc(path: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cpath, err := clone_to_cstring(path, temp_allocator) - if err != nil { return false } - return posix.access(cpath) == .OK -} - -_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 - - switch mode { - case .Read: - if len(p) <= 0 { - return - } - - to_read := uint(min(len(p), MAX_RW)) - _n := i64(posix.read(fd, raw_data(p), to_read)) - switch { - case _n == 0: - err = .EOF - case _n < 0: - err = .Unknown - case: - n = _n - } - return - - case .Read_At: - if len(p) <= 0 { - return - } - - if offset < 0 { - err = .Invalid_Offset - return - } - - to_read := uint(min(len(p), MAX_RW)) - _n := i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset))) - switch { - case _n == 0: - err = .EOF - case _n < 0: - err = .Unknown - case: - n = _n - } - return - - case .Write: - p := p - for len(p) > 0 { - to_write := uint(min(len(p), MAX_RW)) - if _n := i64(posix.write(fd, raw_data(p), to_write)); _n <= 0 { - err = .Unknown - return - } else { - p = p[_n:] - n += _n - } - } - return - - case .Write_At: - p := p - offset := offset - - if offset < 0 { - err = .Invalid_Offset - return - } - - for len(p) > 0 { - to_write := uint(min(len(p), MAX_RW)) - if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); _n <= 0 { - err = .Unknown - return - } else { - p = p[_n:] - n += _n - offset += _n - } - } - return - - case .Seek: - #assert(int(posix.Whence.SET) == int(io.Seek_From.Start)) - #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) - #assert(int(posix.Whence.END) == int(io.Seek_From.End)) - - switch whence { - case .Start, .Current, .End: - break - case: - err = .Invalid_Whence - return - } - - _n := i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) - if _n < 0 { - #partial switch posix.get_errno() { - case .EINVAL: - err = .Invalid_Offset - case: - err = .Unknown - } - return - } - - n = _n - return - - case .Size: - stat: posix.stat_t - if posix.fstat(fd, &stat) != .OK { - err = .Unknown - return - } - - n = i64(stat.st_size) - return - - case .Flush: - err = _sync(&f.file) - return - - case .Close, .Destroy: - err = _close(f) - return - - 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 - } -} diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin deleted file mode 100644 index 521fb345b..000000000 --- a/core/os/os2/file_posix_darwin.odin +++ /dev/null @@ -1,46 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:sys/darwin" -import "core:sys/posix" - -_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - F_GETPATH :: 50 - - buf: [posix.PATH_MAX]byte - if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { - err = _get_platform_error() - return - } - - return clone_to_cstring(string(cstring(&buf[0])), allocator) -} - -_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - csrc := clone_to_cstring(src_path, temp_allocator) or_return - cdst := clone_to_cstring(dst_path, temp_allocator) or_return - - // Disallow directories, as specified by the generic implementation. - - stat: posix.stat_t - if posix.stat(csrc, &stat) != .OK { - err = _get_platform_error() - return - } - - if posix.S_ISDIR(stat.st_mode) { - err = .Invalid_File - return - } - - ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL) - if ret < 0 { - err = _get_platform_error() - } - - return -} \ No newline at end of file diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin deleted file mode 100644 index 05d031930..000000000 --- a/core/os/os2/file_posix_freebsd.odin +++ /dev/null @@ -1,47 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:c" -import "core:sys/posix" - -_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - // NOTE(Feoramund): The situation isn't ideal, but this was the best way I - // could find to implement this. There are a couple outstanding bug reports - // regarding the desire to retrieve an absolute path from a handle, but to - // my knowledge, there hasn't been any work done on it. - // - // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 - // - // This may be unreliable, according to a comment from 2023. - - KInfo_File :: struct { - structsize: c.int, - type: c.int, - fd: c.int, - ref_count: c.int, - flags: c.int, - pad0: c.int, - offset: i64, - - // NOTE(Feoramund): This field represents a complicated union that I am - // avoiding implementing for now. I only need the path data below. - _union: [336]byte, - - path: [posix.PATH_MAX]c.char, - } - - F_KINFO :: 22 - - kinfo: KInfo_File - kinfo.structsize = size_of(KInfo_File) - - res := posix.fcntl(fd, posix.FCNTL_Cmd(F_KINFO), &kinfo) - if res == -1 { - err = _get_platform_error() - return - } - - return clone_to_cstring(string(cstring(&kinfo.path[0])), allocator) -} diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin deleted file mode 100644 index f96c227ba..000000000 --- a/core/os/os2/file_posix_netbsd.odin +++ /dev/null @@ -1,18 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:sys/posix" - -_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - F_GETPATH :: 15 - - buf: [posix.PATH_MAX]byte - if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { - err = _get_platform_error() - return - } - - return clone_to_cstring(string(cstring(&buf[0])), allocator) -} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin deleted file mode 100644 index 8871a0062..000000000 --- a/core/os/os2/file_posix_other.odin +++ /dev/null @@ -1,21 +0,0 @@ -#+private -#+build openbsd -package os2 - -import "base:runtime" - -import "core:sys/posix" - -_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - cname := clone_to_cstring(name, temp_allocator) or_return - - buf: [posix.PATH_MAX]byte - path = posix.realpath(cname, raw_data(buf[:])) - if path == nil { - err = _get_platform_error() - return - } - - return clone_to_cstring(string(path), allocator) -} diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin deleted file mode 100644 index af6e50921..000000000 --- a/core/os/os2/file_stream.odin +++ /dev/null @@ -1,89 +0,0 @@ -package os2 - -import "base:intrinsics" -import "base:runtime" -import "core:io" - -// A subset of the io.Stream_Mode with added File specific modes -File_Stream_Mode :: enum { - Close, - Flush, - Read, - Read_At, - Write, - Write_At, - Seek, - Size, - Destroy, - Query, // query what modes are available on `io.Stream` - - Fstat, // File specific (not available on io.Stream) -} -#assert(intrinsics.type_is_superset_of(File_Stream_Mode, io.Stream_Mode)) - -// Superset interface of io.Stream_Proc with the added `runtime.Allocator` parameter needed for the Fstat mode -File_Stream_Proc :: #type proc( - stream_data: rawptr, - mode: File_Stream_Mode, - p: []byte, - offset: i64, - whence: io.Seek_From, - allocator: runtime.Allocator, -) -> (n: i64, err: Error) - -File_Stream :: struct { - procedure: File_Stream_Proc, - data: rawptr, -} - - -// Converts a file `f` into an `io.Stream` -to_stream :: proc(f: ^File) -> (s: io.Stream) { - if f != nil { - assert(f.stream.procedure != nil) - s = { - file_io_stream_proc, - f, - } - } - return -} - -/* - This is an alias of `to_stream` which converts a file `f` to an `io.Stream`. - It can be useful to indicate what the stream is meant to be used for as a writer, - even if it has no logical difference. -*/ -to_writer :: to_stream - -/* - This is an alias of `to_stream` which converts a file `f` to an `io.Stream`. - It can be useful to indicate what the stream is meant to be used for as a reader, - even if it has no logical difference. -*/ -to_reader :: to_stream - - -@(private="package") -file_io_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) - - file_stream_mode := transmute(File_Stream_Mode)mode - - ferr: Error - n, ferr = f.stream.procedure(f, file_stream_mode, p, offset, whence, runtime.nil_allocator()) - err = error_to_io_error(ferr) - return -} - -@(private="package") -file_stream_fstat_utility :: proc(f: ^File_Impl, p: []byte, allocator: runtime.Allocator) -> (err: Error) { - fi: File_Info - if len(p) >= size_of(fi) { - fi, err = _fstat(&f.file, allocator) - runtime.mem_copy_non_overlapping(raw_data(p), &fi, size_of(fi)) - } else { - err = .Short_Buffer - } - return -} \ No newline at end of file diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin deleted file mode 100644 index f81dc2190..000000000 --- a/core/os/os2/file_util.odin +++ /dev/null @@ -1,257 +0,0 @@ -package os2 - -import "base:runtime" -import "core:strconv" -import "core:unicode/utf8" - -/* - `write_string` writes a string `s` to file `f`. - Returns the number of bytes written and an error, if any is encountered. -*/ -write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { - return write(f, transmute([]byte)s) -} - -/* - `write_strings` writes a variadic list of strings `strings` to file `f`. - Returns the number of bytes written and an error, if any is encountered. -*/ -write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) { - for s in strings { - m: int - m, err = write_string(f, s) - n += m - if err != nil { - return - } - } - return -} -/* - `write_byte` writes a byte `b` to file `f`. - Returns the number of bytes written and an error, if any is encountered. -*/ -write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { - return write(f, []byte{b}) -} - -/* - `write_rune` writes a rune `r` as an UTF-8 encoded string to file `f`. - Returns the number of bytes written and an error, if any is encountered. -*/ -write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { - if r < utf8.RUNE_SELF { - return write_byte(f, byte(r)) - } - - b: [4]byte - b, n = utf8.encode_rune(r) - return write(f, b[:n]) -} - -/* - `write_encoded_rune` writes a rune `r` as an UTF-8 encoded string which with escaped control codes to file `f`. - Returns the number of bytes written and an error, if any is encountered. -*/ -write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { - wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { - n^ += m - if merr != nil { - err^ = merr - return true - } - return false - } - - if wrap(write_byte(f, '\''), &n, &err) { return } - - switch r { - case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } - case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } - case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } - case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } - case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } - case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } - case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } - case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } - case: - if r < 32 { - if wrap(write_string(f, "\\x"), &n, &err) { return } - b: [2]byte - s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) - switch len(s) { - case 0: if wrap(write_string(f, "00"), &n, &err) { return } - case 1: if wrap(write_rune(f, '0'), &n, &err) { return } - case 2: if wrap(write_string(f, s), &n, &err) { return } - } - } else { - if wrap(write_rune(f, r), &n, &err) { return } - } - } - _ = wrap(write_byte(f, '\''), &n, &err) - return -} - -/* - `write_ptr` is a utility procedure that writes the bytes points at `data` with length `len`. - - It is equivalent to: `write(f, ([^]byte)(data)[:len])` -*/ -write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { - return write(f, ([^]byte)(data)[:len]) -} - -/* - `read_ptr` is a utility procedure that reads the bytes points at `data` with length `len`. - - It is equivalent to: `read(f, ([^]byte)(data)[:len])` -*/ -read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { - return read(f, ([^]byte)(data)[:len]) -} - - - -/* - `read_at_least` reads from `f` into `buf` until it has read at least `min` bytes. - It returns the number of bytes copied and an error if fewer bytes were read. - The error is only an `io.EOF` if no bytes were read. -*/ -read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) { - if len(buf) < min { - return 0, .Short_Buffer - } - nn := max(int) - for nn > 0 && n < min && err == nil { - nn, err = read(f, buf[n:]) - n += nn - } - if n >= min { - err = nil - } - return -} - -/* - `read_full` reads exactly `len(buf)` bytes from `f` into `buf`. - It returns the number of bytes copied and an error if fewer bytes were read. - The error is only an `io.EOF` if no bytes were read. - - It is equivalent to `read_at_least(f, buf, len(buf))`. -*/ -read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) { - return read_at_least(f, buf, len(buf)) -} - - - -read_entire_file :: proc{ - read_entire_file_from_path, - read_entire_file_from_file, -} - -/* - `read_entire_file_from_path` reads the entire named file `name` into memory allocated with `allocator`. - A slice of bytes and an error is returned, if any error is encountered. -*/ -@(require_results) -read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) { - f, ferr := open(name) - if ferr != nil { - return nil, ferr - } - defer close(f) - return read_entire_file_from_file(f=f, allocator=allocator, loc=loc) -} - -/* - `read_entire_file_from_file` reads the entire file `f` into memory allocated with `allocator`. - A slice of bytes and an error is returned, if any error is encountered. -*/ -@(require_results) -read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) { - size: int - has_size := false - if size64, serr := file_size(f); serr == nil { - if i64(int(size64)) == size64 { - has_size = true - size = int(size64) - } - } - - if has_size && size > 0 { - total: int - data = make([]byte, size, allocator, loc) or_return - for total < len(data) { - n: int - n, err = read(f, data[total:]) - total += n - if err != nil { - if err == .EOF { - err = nil - } - data = data[:total] - break - } - } - return - } else { - buffer: [1024]u8 - out_buffer := make([dynamic]u8, 0, 0, allocator, loc) - total := 0 - for { - n: int - n, err = read(f, buffer[:]) - total += n - append_elems(&out_buffer, ..buffer[:n], loc=loc) or_return - if err != nil { - if err == .EOF || err == .Broken_Pipe { - err = nil - } - data = out_buffer[:total] - return - } - } - } -} - -/* - `write_entire_file` writes the contents of `data` into named file `name`. - It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. - An error is returned if any is encountered. -*/ -write_entire_file :: proc{ - write_entire_file_from_bytes, - write_entire_file_from_string, -} - -/* - `write_entire_file_from_bytes` writes the contents of `data` into named file `name`. - It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. - An error is returned if any is encountered. -*/ -@(require_results) -write_entire_file_from_bytes :: proc(name: string, data: []byte, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error { - flags := O_WRONLY|O_CREATE - if truncate { - flags |= O_TRUNC - } - f := open(name, flags, perm) or_return - _, err := write(f, data) - if cerr := close(f); cerr != nil && err == nil { - err = cerr - } - return err -} - - - -/* - `write_entire_file_from_string` writes the contents of `data` into named file `name`. - It defaults with the permssions `perm := Permissions_Read_All + {.Write_User}`, and `truncate`s by default. - An error is returned if any is encountered. -*/ -@(require_results) -write_entire_file_from_string :: proc(name: string, data: string, perm := Permissions_Read_All + {.Write_User}, truncate := true) -> Error { - return write_entire_file(name, transmute([]byte)data, perm, truncate) -} diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin deleted file mode 100644 index 78aa90699..000000000 --- a/core/os/os2/file_wasi.odin +++ /dev/null @@ -1,570 +0,0 @@ -#+feature global-context -#+private -package os2 - -import "base:runtime" - -import "core:io" -import "core:sys/wasm/wasi" -import "core:time" - -// NOTE: Don't know if there is a max in wasi. -MAX_RW :: 1 << 30 - -File_Impl :: struct { - file: File, - name: string, - fd: wasi.fd_t, - allocator: runtime.Allocator, -} - -// WASI works with "preopened" directories, the environment retrieves directories -// (for example with `wasmtime --dir=. module.wasm`) and those given directories -// are the only ones accessible by the application. -// -// So in order to facilitate the `os` API (absolute paths etc.) we keep a list -// of the given directories and match them when needed (notably `os.open`). -Preopen :: struct { - fd: wasi.fd_t, - prefix: string, -} -preopens: []Preopen - -@(init) -init_std_files :: proc "contextless" () { - new_std :: proc "contextless" (impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { - impl.file.impl = impl - impl.allocator = runtime.nil_allocator() - impl.fd = fd - impl.name = string(name) - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - return &impl.file - } - - @(static) files: [3]File_Impl - stdin = new_std(&files[0], 0, "/dev/stdin") - stdout = new_std(&files[1], 1, "/dev/stdout") - stderr = new_std(&files[2], 2, "/dev/stderr") -} - -@(init) -init_preopens :: proc "contextless" () { - strip_prefixes :: proc "contextless" (path: string) -> string { - path := path - loop: for len(path) > 0 { - switch { - case path[0] == '/': - path = path[1:] - case len(path) > 2 && path[0] == '.' && path[1] == '/': - path = path[2:] - case len(path) == 1 && path[0] == '.': - path = path[1:] - case: - break loop - } - } - return path - } - - context = runtime.default_context() - - n: int - n_loop: for fd := wasi.fd_t(3); ; fd += 1 { - _, err := wasi.fd_prestat_get(fd) - #partial switch err { - case .BADF: break n_loop - case .SUCCESS: n += 1 - case: - print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get") - break n_loop - } - } - - alloc_err: runtime.Allocator_Error - preopens, alloc_err = make([]Preopen, n, file_allocator()) - if alloc_err != nil { - print_error(stderr, alloc_err, "could not allocate memory for wasi preopens") - return - } - - loop: for &preopen, i in preopens { - fd := wasi.fd_t(3 + i) - - desc, err := wasi.fd_prestat_get(fd) - assert(err == .SUCCESS) - - switch desc.tag { - case .DIR: - buf: []byte - buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator()) - if alloc_err != nil { - print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name") - continue loop - } - - if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { - print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name") - continue loop - } - - preopen.fd = fd - preopen.prefix = strip_prefixes(string(buf)) - } - } -} - -@(require_results) -match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { - @(require_results) - prefix_matches :: proc(prefix, path: string) -> bool { - // Empty is valid for any relative path. - if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { - return true - } - - if len(path) < len(prefix) { - return false - } - - if path[:len(prefix)] != prefix { - return false - } - - // Only match on full components. - i := len(prefix) - for i > 0 && prefix[i-1] == '/' { - i -= 1 - } - return path[i] == '/' - } - - path := path - if path == "" { - return 0, "", false - } - - for len(path) > 0 && path[0] == '/' { - path = path[1:] - } - - match: Preopen - #reverse for preopen in preopens { - if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { - match = preopen - } - } - - if match.fd == 0 { - return 0, "", false - } - - relative := path[len(match.prefix):] - for len(relative) > 0 && relative[0] == '/' { - relative = relative[1:] - } - - if len(relative) == 0 { - relative = "." - } - - return match.fd, relative, true -} - -_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { - dir_fd, relative, ok := match_preopen(name) - if !ok { - return nil, .Invalid_Path - } - - oflags: wasi.oflags_t - if .Create in flags { oflags += {.CREATE} } - if .Excl in flags { oflags += {.EXCL} } - if .Trunc in flags { oflags += {.TRUNC} } - - fdflags: wasi.fdflags_t - if .Append in flags { fdflags += {.APPEND} } - if .Sync in flags { fdflags += {.SYNC} } - if .Non_Blocking in flags { fdflags += {.NONBLOCK} } - - // NOTE: rights are adjusted to what this package's functions might want to call. - rights: wasi.rights_t - if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} } - if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} } - - fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) - if fderr != nil { - err = _get_platform_error(fderr) - return - } - - return _new_file(uintptr(fd), name, file_allocator()) -} - -_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - impl := new(File_Impl, allocator) or_return - defer if err != nil { free(impl, allocator) } - - impl.allocator = allocator - // NOTE: wasi doesn't really do full paths afact. - impl.name = clone_string(name, allocator) or_return - impl.fd = wasi.fd_t(handle) - impl.file.impl = impl - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - - return &impl.file, nil -} - -_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { - if f == nil || f.impl == nil { - return - } - - dir_fd, relative, ok := match_preopen(name(f)) - if !ok { - return nil, .Invalid_Path - } - - fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, {}, {}, {}, {}) - if fderr != nil { - err = _get_platform_error(fderr) - return - } - defer if err != nil { wasi.fd_close(fd) } - - fderr = wasi.fd_renumber((^File_Impl)(f.impl).fd, fd) - if fderr != nil { - err = _get_platform_error(fderr) - return - } - - return _new_file(uintptr(fd), name(f), file_allocator()) -} - -_close :: proc(f: ^File_Impl) -> (err: Error) { - if errno := wasi.fd_close(f.fd); errno != nil { - err = _get_platform_error(errno) - } - - delete(f.name, f.allocator) - free(f, f.allocator) - return -} - -_fd :: proc(f: ^File) -> uintptr { - return uintptr(__fd(f)) -} - -__fd :: proc(f: ^File) -> wasi.fd_t { - if f != nil && f.impl != nil { - return (^File_Impl)(f.impl).fd - } - 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 - } - return "" -} - -_sync :: proc(f: ^File) -> Error { - return _get_platform_error(wasi.fd_sync(__fd(f))) -} - -_truncate :: proc(f: ^File, size: i64) -> Error { - return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size))) -} - -_remove :: proc(name: string) -> Error { - dir_fd, relative, ok := match_preopen(name) - if !ok { - return .Invalid_Path - } - - err := wasi.path_remove_directory(dir_fd, relative) - if err == .NOTDIR { - err = wasi.path_unlink_file(dir_fd, relative) - } - - return _get_platform_error(err) -} - -_rename :: proc(old_path, new_path: string) -> Error { - src_dir_fd, src_relative, src_ok := match_preopen(old_path) - if !src_ok { - return .Invalid_Path - } - - new_dir_fd, new_relative, new_ok := match_preopen(new_path) - if !new_ok { - return .Invalid_Path - } - - return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative)) -} - -_link :: proc(old_name, new_name: string) -> Error { - src_dir_fd, src_relative, src_ok := match_preopen(old_name) - if !src_ok { - return .Invalid_Path - } - - new_dir_fd, new_relative, new_ok := match_preopen(new_name) - if !new_ok { - return .Invalid_Path - } - - return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative)) -} - -_symlink :: proc(old_name, new_name: string) -> Error { - src_dir_fd, src_relative, src_ok := match_preopen(old_name) - if !src_ok { - return .Invalid_Path - } - - new_dir_fd, new_relative, new_ok := match_preopen(new_name) - if !new_ok { - return .Invalid_Path - } - - if src_dir_fd != new_dir_fd { - return .Invalid_Path - } - - return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative)) -} - -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - dir_fd, relative, ok := match_preopen(name) - if !ok { - return "", .Invalid_Path - } - - n, _err := wasi.path_readlink(dir_fd, relative, nil) - if _err != nil { - err = _get_platform_error(_err) - return - } - - buf := make([]byte, n, allocator) or_return - - _, _err = wasi.path_readlink(dir_fd, relative, buf) - s = string(buf) - err = _get_platform_error(_err) - return -} - -_chdir :: proc(name: string) -> Error { - return .Unsupported -} - -_fchdir :: proc(f: ^File) -> Error { - return .Unsupported -} - -_fchmod :: proc(f: ^File, mode: Permissions) -> Error { - return .Unsupported -} - -_chmod :: proc(name: string, mode: Permissions) -> 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 { - dir_fd, relative, ok := match_preopen(name) - if !ok { - return .Invalid_Path - } - - _atime := wasi.timestamp_t(atime._nsec) - _mtime := wasi.timestamp_t(mtime._nsec) - - return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM})) -} - -_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - _atime := wasi.timestamp_t(atime._nsec) - _mtime := wasi.timestamp_t(mtime._nsec) - - return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM})) -} - -_exists :: proc(path: string) -> bool { - dir_fd, relative, ok := match_preopen(path) - if !ok { - return false - } - - _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) - if err != nil { - return false - } - - return true -} - -_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 - - switch mode { - case .Read: - if len(p) <= 0 { - return - } - - to_read := min(len(p), MAX_RW) - _n, _err := wasi.fd_read(fd, {p[:to_read]}) - n = i64(_n) - - if _err != nil { - err = .Unknown - } else if n == 0 { - err = .EOF - } - - return - - case .Read_At: - if len(p) <= 0 { - return - } - - if offset < 0 { - err = .Invalid_Offset - return - } - - to_read := min(len(p), MAX_RW) - _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset)) - n = i64(_n) - - if _err != nil { - err = .Unknown - } else if n == 0 { - err = .EOF - } - - return - - case .Write: - p := p - for len(p) > 0 { - to_write := min(len(p), MAX_RW) - _n, _err := wasi.fd_write(fd, {p[:to_write]}) - if _err != nil { - err = .Unknown - return - } - p = p[_n:] - n += i64(_n) - } - return - - case .Write_At: - p := p - offset := offset - - if offset < 0 { - err = .Invalid_Offset - return - } - - for len(p) > 0 { - to_write := min(len(p), MAX_RW) - _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset)) - if _err != nil { - err = .Unknown - return - } - - p = p[_n:] - n += i64(_n) - offset += i64(_n) - } - return - - case .Seek: - #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start)) - #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current)) - #assert(int(wasi.whence_t.END) == int(io.Seek_From.End)) - - switch whence { - case .Start, .Current, .End: - break - case: - err = .Invalid_Whence - return - } - - _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence)) - #partial switch _err { - case .INVAL: - err = .Invalid_Offset - case: - err = .Unknown - case .SUCCESS: - n = i64(_n) - } - return - - case .Size: - stat, _err := wasi.fd_filestat_get(fd) - if _err != nil { - err = .Unknown - return - } - - n = i64(stat.size) - return - - case .Flush: - ferr := _sync(&f.file) - err = error_to_io_error(ferr) - return - - case .Close, .Destroy: - ferr := _close(f) - err = error_to_io_error(ferr) - return - - 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 - } -} diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin deleted file mode 100644 index 0e3448dd7..000000000 --- a/core/os/os2/file_windows.odin +++ /dev/null @@ -1,995 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:io" -import "core:mem" -import "core:sync" -import "core:time" -import "core:unicode/utf16" -import win32 "core:sys/windows" - -INVALID_HANDLE :: ~uintptr(0) - -_ERROR_BAD_NETPATH :: 53 -MAX_RW :: 1<<30 - - -File_Impl_Kind :: enum u8 { - File, - Console, - Pipe, -} - -File_Impl :: struct { - file: File, - - fd: rawptr, - name: string, - wname: win32.wstring, - kind: File_Impl_Kind, - - allocator: runtime.Allocator, - - r_buf: []byte, - w_buf: []byte, - w_n: int, - max_consecutive_empty_writes: int, - - rw_mutex: sync.RW_Mutex, // read write calls - p_mutex: sync.Mutex, // pread pwrite calls -} - -@(init) -init_std_files :: proc "contextless" () { - new_std :: proc "contextless" (impl: ^File_Impl, code: u32, name: string) -> ^File { - impl.file.impl = impl - - impl.allocator = runtime.nil_allocator() - impl.fd = win32.GetStdHandle(code) - impl.name = name - impl.wname = nil - - handle := _handle(&impl.file) - kind := File_Impl_Kind.File - if m: u32; win32.GetConsoleMode(handle, &m) { - kind = .Console - } - if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { - kind = .Pipe - } - impl.kind = kind - - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - - return &impl.file - } - - @(static) files: [3]File_Impl - stdin = new_std(&files[0], win32.STD_INPUT_HANDLE, "") - stdout = new_std(&files[1], win32.STD_OUTPUT_HANDLE, "") - stderr = new_std(&files[2], win32.STD_ERROR_HANDLE, "") -} - -_handle :: proc "contextless" (f: ^File) -> win32.HANDLE { - return win32.HANDLE(_fd(f)) -} - -_open_internal :: proc(name: string, flags: File_Flags, perm: Permissions) -> (handle: uintptr, err: Error) { - if len(name) == 0 { - err = .Not_Exist - return - } - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - path := _fix_long_path(name, temp_allocator) or_return - access: u32 - switch flags & {.Read, .Write} { - case {.Read}: access = win32.FILE_GENERIC_READ - case {.Write}: access = win32.FILE_GENERIC_WRITE - case {.Read, .Write}: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE - } - - if .Create in flags { - access |= win32.FILE_GENERIC_WRITE - } - if .Append in flags { - access &~= win32.FILE_GENERIC_WRITE - access |= win32.FILE_APPEND_DATA - } - share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) - sa := win32.SECURITY_ATTRIBUTES { - nLength = size_of(win32.SECURITY_ATTRIBUTES), - bInheritHandle = .Inheritable in flags, - } - - create_mode: u32 = win32.OPEN_EXISTING - switch { - case flags & {.Create, .Excl} == {.Create, .Excl}: - create_mode = win32.CREATE_NEW - case flags & {.Create, .Trunc} == {.Create, .Trunc}: - create_mode = win32.CREATE_ALWAYS - case flags & {.Create} == {.Create}: - create_mode = win32.OPEN_ALWAYS - case flags & {.Trunc} == {.Trunc}: - create_mode = win32.TRUNCATE_EXISTING - } - - attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS - if .Write_User not_in perm { - attrs = win32.FILE_ATTRIBUTE_READONLY - if create_mode == win32.CREATE_ALWAYS { - // NOTE(bill): Open has just asked to create a file in read-only mode. - // If the file already exists, to make it akin to a *nix open call, - // the call preserves the existing permissions. - nix_attrs := win32.FILE_ATTRIBUTE_NORMAL - if .Non_Blocking in flags { - nix_attrs |= win32.FILE_FLAG_OVERLAPPED - } - h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, nix_attrs, nil) - if h == win32.INVALID_HANDLE { - switch e := win32.GetLastError(); e { - case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: - // file does not exist, create the file - case 0: - return uintptr(h), nil - case: - return 0, _get_platform_error() - } - } - } - } - - if .Non_Blocking in flags { - attrs |= win32.FILE_FLAG_OVERLAPPED - } - - h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) - if h == win32.INVALID_HANDLE { - return 0, _get_platform_error() - } - return uintptr(h), nil -} - - -_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { - flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags, perm) or_return - return _new_file(handle, name, file_allocator()) -} - -_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { - if handle == INVALID_HANDLE { - return - } - impl := new(File_Impl, allocator) or_return - defer if err != nil { - free(impl, allocator) - } - - impl.file.impl = impl - - impl.allocator = allocator - impl.fd = rawptr(handle) - impl.name = clone_string(name, impl.allocator) or_return - impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return - - handle := _handle(&impl.file) - kind := File_Impl_Kind.File - if m: u32; win32.GetConsoleMode(handle, &m) { - kind = .Console - } - if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { - kind = .Pipe - } - impl.kind = kind - - impl.file.stream = { - data = impl, - procedure = _file_stream_proc, - } - - return &impl.file, nil -} - - -@(require_results) -_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm: Permissions) -> (f: ^File, err: Error) { - assert(buffer_size > 0) - flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags, perm) or_return - return _new_file_buffered(handle, name, buffer_size) -} - -_new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) -> (f: ^File, err: Error) { - f, err = _new_file(handle, name, file_allocator()) - if f != nil && err == nil { - impl := (^File_Impl)(f.impl) - impl.r_buf = make([]byte, buffer_size, file_allocator()) - impl.w_buf = make([]byte, buffer_size, file_allocator()) - } - return -} - -_clone :: proc(f: ^File) -> (clone: ^File, err: Error) { - if f == nil || f.impl == nil { - return - } - - clonefd: win32.HANDLE - process := win32.GetCurrentProcess() - if !win32.DuplicateHandle( - process, - win32.HANDLE(_fd(f)), - process, - &clonefd, - 0, - false, - win32.DUPLICATE_SAME_ACCESS, - ) { - err = _get_platform_error() - return - } - defer if err != nil { win32.CloseHandle(clonefd) } - - return _new_file(uintptr(clonefd), name(f), file_allocator()) -} - -_fd :: proc "contextless" (f: ^File) -> uintptr { - if f == nil || f.impl == nil { - return INVALID_HANDLE - } - 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 - } - - a := f.allocator - err0 := free(rawptr(f.wname), a) - err1 := delete(f.name, a) - err2 := delete(f.r_buf, a) - err3 := delete(f.w_buf, a) - err4 := free(f, a) - err0 or_return - err1 or_return - err2 or_return - err3 or_return - err4 or_return - return nil -} - - -_close :: proc(f: ^File_Impl) -> Error { - if f == nil { - return nil - } - if !win32.CloseHandle(win32.HANDLE(f.fd)) { - return .Closed - } - return _destroy(f) -} - -_name :: proc(f: ^File) -> string { - return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" -} - -_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - handle := _handle(&f.file) - if handle == win32.INVALID_HANDLE { - return 0, .Invalid_File - } - - if f.kind == .Pipe { - return 0, .Invalid_File - } - - sync.guard(&f.rw_mutex) - - w: u32 - switch whence { - case .Start: w = win32.FILE_BEGIN - case .Current: w = win32.FILE_CURRENT - case .End: w = win32.FILE_END - case: - return 0, .Invalid_Whence - } - hi := i32(offset>>32) - lo := i32(offset) - - dw_ptr := win32.SetFilePointer(handle, lo, &hi, w) - if dw_ptr == win32.INVALID_SET_FILE_POINTER { - return 0, _get_platform_error() - } - return i64(hi)<<32 + i64(dw_ptr), nil -} - -_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { - return _read_internal(f, p) -} - -_read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { - length := len(p) - if length == 0 { - return - } - - read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { - if len(b) == 0 { - return 0, nil - } - - // TODO(bill): should this be moved to `File_Impl` instead? - BUF_SIZE :: 386 - buf16: [BUF_SIZE]u16 - buf8: [4*BUF_SIZE]u8 - - for n < len(b) && err == nil { - min_read := max(len(b)/4, 1 if len(b) > 0 else 0) - max_read := u32(min(BUF_SIZE, min_read)) - if max_read == 0 { - break - } - - single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = _get_platform_error() - } - - buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) - src := buf8[:buf8_len] - - ctrl_z := false - for i := 0; i < len(src) && n+i < len(b); i += 1 { - x := src[i] - if x == 0x1a { // ctrl-z - ctrl_z = true - break - } - b[n] = x - n += 1 - } - if ctrl_z || single_read_length < max_read { - break - } - - // NOTE(bill): if the last two values were a newline, then it is expected that - // this is the end of the input - if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { - break - } - } - - return - } - - handle := _handle(&f.file) - - total_read: int - - sync.shared_guard(&f.rw_mutex) // multiple readers - - if sync.guard(&f.p_mutex) { - to_read := win32.DWORD(min(length, MAX_RW)) - switch f.kind { - case .Console: - // NOTE(laytan): at least for now, just use ReadFile, it seems to work fine, - // but, there may be issues with certain situations that we need to get reproductions for. - // total_read, err = read_console(handle, p[total_read:][:to_read]) - fallthrough - case .Pipe, .File: - single_read_length: win32.DWORD - ok := win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) - if ok { - total_read += int(single_read_length) - } else { - err = _get_platform_error() - } - } - } - - if total_read == 0 && err == nil { - // ok and 0 bytes means EOF: - // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file - err = .EOF - } - - return i64(total_read), err -} - -_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { - pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - - } - curr_offset := _seek(f, 0, .Current) or_return - defer _seek(f, curr_offset, .Start) - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - // TODO(bill): Determine the correct behaviour for consoles - - h := _handle(&f.file) - done: win32.DWORD - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - err = _get_platform_error() - done = 0 - } - n = i64(done) - return - } - - sync.guard(&f.p_mutex) - - p, offset := p, offset - for len(p) > 0 { - m := pread(f, p, offset) or_return - n += m - p = p[m:] - offset += i64(m) - } - return -} - -_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { - return _write_internal(f, p) -} -_write_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { - if len(p) == 0 { - return - } - - single_write_length: win32.DWORD - total_write: i64 - length := i64(len(p)) - - handle := _handle(&f.file) - - sync.guard(&f.rw_mutex) - for total_write < length { - remaining := length - total_write - to_write := win32.DWORD(min(i32(remaining), MAX_RW)) - - e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - n = i64(total_write) - err = _get_platform_error() - return - } - total_write += i64(single_write_length) - } - return i64(total_write), nil -} - -_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { - pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - - } - curr_offset := _seek(f, 0, .Current) or_return - defer _seek(f, curr_offset, .Start) - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - h := _handle(&f.file) - done: win32.DWORD - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - err = _get_platform_error() - done = 0 - } - n = i64(done) - return - } - - sync.guard(&f.p_mutex) - p, offset := p, offset - for len(p) > 0 { - m := pwrite(f, p, offset) or_return - n += m - p = p[m:] - offset += i64(m) - } - return -} - -_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { - length: win32.LARGE_INTEGER - handle := _handle(&f.file) - if f.kind == .Pipe { - bytes_available: u32 - if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { - return i64(bytes_available), nil - } else { - err = _get_platform_error() - return - } - } - if !win32.GetFileSizeEx(handle, &length) { - err = _get_platform_error() - } - n = i64(length) - return -} - - -_sync :: proc(f: ^File) -> Error { - if f != nil && f.impl != nil { - return _flush_internal((^File_Impl)(f.impl)) - } - return nil -} - -_flush :: proc(f: ^File_Impl) -> Error { - return _flush_internal(f) -} -_flush_internal :: proc(f: ^File_Impl) -> Error { - handle := _handle(&f.file) - if !win32.FlushFileBuffers(handle) { - return _get_platform_error() - } - return nil -} - -_truncate :: proc(f: ^File, size: i64) -> Error { - if f == nil || f.impl == nil { - return nil - } - curr_off := seek(f, 0, .Current) or_return - defer seek(f, curr_off, .Start) - seek(f, size, .Start) or_return - handle := _handle(f) - if !win32.SetEndOfFile(handle) { - return _get_platform_error() - } - return nil -} - -_remove :: proc(name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - p := _fix_long_path(name, temp_allocator) or_return - err, err1: Error - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } - if err == nil { - return nil - } - if !win32.RemoveDirectoryW(p) { - err1 = _get_platform_error() - } - if err1 == nil { - return nil - } - - if err != err1 { - a := win32.GetFileAttributesW(p) - if a == win32.INVALID_FILE_ATTRIBUTES { - err = _get_platform_error() - } else { - if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - err = err1 - } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { - if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = nil - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } - } - } - } - } - - return err -} - -_rename :: proc(old_path, new_path: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - from := _fix_long_path(old_path, temp_allocator) or_return - to := _fix_long_path(new_path, temp_allocator) or_return - if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - return nil - } - return _get_platform_error() - -} - -_link :: proc(old_name, new_name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - o := _fix_long_path(old_name, temp_allocator) or_return - n := _fix_long_path(new_name, temp_allocator) or_return - if win32.CreateHardLinkW(n, o, nil) { - return nil - } - return _get_platform_error() -} - -_symlink :: proc(old_name, new_name: string) -> Error { - return .Unsupported -} - -_open_sym_link :: proc(p: cstring16) -> (handle: win32.HANDLE, err: Error) { - attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) - attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT - handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) - if handle == win32.INVALID_HANDLE { - return nil, _get_platform_error() - } - return - -} - -_normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: string, err: Error) { - has_prefix :: proc(p: []u16, str: string) -> bool { - if len(p) < len(str) { - return false - } - // assume ascii - for i in 0.. bool { - return has_prefix(p, `\??\`) - } - - if !has_unc_prefix(p) { - return win32_utf16_to_utf8(p, allocator) - } - - ws := p[4:] - switch { - case len(ws) >= 2 && ws[1] == ':': - return win32_utf16_to_utf8(ws, allocator) - case has_prefix(ws, `UNC\`): - ws[3] = '\\' // override data in buffer - return win32_utf16_to_utf8(ws[3:], allocator) - } - - - handle := _open_sym_link(cstring16(raw_data(p))) or_return - defer win32.CloseHandle(handle) - - n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS) - if n == 0 { - return "", _get_platform_error() - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := make([]u16, n+1, temp_allocator) - n = win32.GetFinalPathNameByHandleW(handle, cstring16(raw_data(buf)), u32(len(buf)), win32.VOLUME_NAME_DOS) - if n == 0 { - return "", _get_platform_error() - } - - ws = buf[:n] - if has_unc_prefix(ws) { - ws = ws[4:] - if len(ws) > 3 && has_prefix(ws, `UNC`) { - ws[2] = '\\' - return win32_utf16_to_utf8(ws[2:], allocator) - } - return win32_utf16_to_utf8(ws, allocator) - } - return "", .Invalid_Path -} - -_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024 - - @thread_local - rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - p := _fix_long_path(name, temp_allocator) or_return - handle := _open_sym_link(p) or_return - defer win32.CloseHandle(handle) - - bytes_returned: u32 - if !win32.DeviceIoControl(handle, win32.FSCTL_GET_REPARSE_POINT, nil, 0, &rdb_buf[0], len(rdb_buf)-1, &bytes_returned, nil) { - err = _get_platform_error() - return - } - mem.zero_slice(rdb_buf[:min(bytes_returned+1, len(rdb_buf))]) - - - rdb := (^win32.REPARSE_DATA_BUFFER)(&rdb_buf[0]) - switch rdb.ReparseTag { - case win32.IO_REPARSE_TAG_SYMLINK: - rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest) - pb := ([^]u16)(&rb.PathBuffer) - pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 - p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] - if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { - return win32_utf16_to_utf8(p, allocator) - } - return _normalize_link_path(p, allocator) - - case win32.IO_REPARSE_TAG_MOUNT_POINT: - rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest) - pb := ([^]u16)(&rb.PathBuffer) - pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 - p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] - return _normalize_link_path(p, allocator) - } - // Path wasn't a symlink/junction but another reparse point kind - return "", nil -} - - -_fchdir :: proc(f: ^File) -> Error { - if f == nil || f.impl == nil { - return nil - } - impl := (^File_Impl)(f.impl) - if !win32.SetCurrentDirectoryW(impl.wname) { - return _get_platform_error() - } - return nil -} - -_fchmod :: proc(f: ^File, mode: Permissions) -> Error { - if f == nil || f.impl == nil { - return nil - } - d: win32.BY_HANDLE_FILE_INFORMATION - if !win32.GetFileInformationByHandle(_handle(f), &d) { - return _get_platform_error() - } - attrs := d.dwFileAttributes - if .Write_User in mode { - attrs &~= win32.FILE_ATTRIBUTE_READONLY - } else { - attrs |= win32.FILE_ATTRIBUTE_READONLY - } - - info: win32.FILE_BASIC_INFO - info.FileAttributes = attrs - if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { - return _get_platform_error() - } - return nil -} - -_fchown :: proc(f: ^File, uid, gid: int) -> Error { - return .Unsupported -} - -_chdir :: proc(name: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - p := _fix_long_path(name, temp_allocator) or_return - if !win32.SetCurrentDirectoryW(p) { - return _get_platform_error() - } - return nil -} - -_chmod :: proc(name: string, mode: Permissions) -> Error { - f := open(name, {.Write}) or_return - defer close(f) - return _fchmod(f, mode) -} - -_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 { - f := open(name, {.Write}) or_return - defer close(f) - return _fchtimes(f, atime, mtime) -} - -_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - if f == nil || f.impl == nil { - return nil - } - - atime, mtime := atime, mtime - if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { - atime = mtime - } - - info: win32.FILE_BASIC_INFO - info.LastAccessTime = time_as_filetime(atime) - info.LastWriteTime = time_as_filetime(mtime) - if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) { - return _get_platform_error() - } - return nil -} - -_exists :: proc(path: string) -> bool { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - wpath, _ := _fix_long_path(path, temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - return attribs != win32.INVALID_FILE_ATTRIBUTES -} - -@(private="package") -_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) - switch mode { - case .Read: - n, err = _read(f, p) - return - case .Read_At: - n, err = _read_at(f, p, offset) - return - case .Write: - n, err = _write(f, p) - return - case .Write_At: - n, err = _write_at(f, p, offset) - return - case .Seek: - n, err = _seek(f, offset, whence) - return - case .Size: - n, err = _file_size(f) - return - case .Flush: - err = _flush(f) - return - case .Close, .Destroy: - err = _close(f) - return - 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 - } - return 0, .Unsupported -} - - - - -@(private="package", require_results) -win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: cstring16, err: runtime.Allocator_Error) { - ws = cstring16(raw_data(win32_utf8_to_utf16(s, allocator) or_return)) - return -} - -@(private="package", require_results) -win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) { - if len(s) < 1 { - return - } - - b := transmute([]byte)s - cstr := raw_data(b) - n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) - if n == 0 { - return nil, nil - } - - text := make([]u16, n+1, allocator) or_return - - n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n) - if n1 == 0 { - delete(text, allocator) - return - } - - text[n] = 0 - for n >= 1 && text[n-1] == 0 { - n -= 1 - } - ws = text[:n] - return -} - -@(private="package", require_results) -win32_wstring_to_utf8 :: proc(s: cstring16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { - if s == nil || s == "" { - return "", nil - } - return win32_utf16_to_utf8(string16(s), allocator) -} - -@(private="package") -win32_utf16_to_utf8 :: proc{ - win32_utf16_string16_to_utf8, - win32_utf16_u16_to_utf8, -} - -@(private="package", require_results) -win32_utf16_string16_to_utf8 :: proc(s: string16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { - if len(s) == 0 { - return - } - - n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) - if n == 0 { - return - } - - // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated - // and will scan it to find the first null terminated character. The resulting string will - // also be null terminated. - // If N > 0 it assumes the wide string is not null terminated and the resulting string - // will not be null terminated. - text := make([]byte, n, allocator) or_return - - n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) - if n1 == 0 { - delete(text, allocator) - return - } - - for i in 0.. (res: string, err: runtime.Allocator_Error) { - if len(s) == 0 { - return - } - - n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) - if n == 0 { - return - } - - // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated - // and will scan it to find the first null terminated character. The resulting string will - // also be null terminated. - // If N > 0 it assumes the wide string is not null terminated and the resulting string - // will not be null terminated. - text := make([]byte, n, allocator) or_return - - n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) - if n1 == 0 { - delete(text, allocator) - return - } - - for i in 0.. runtime.Allocator { - return runtime.Allocator{ - procedure = heap_allocator_proc, - data = nil, - } -} - - -@(require_results) -heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { - return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) -} diff --git a/core/os/os2/heap_js.odin b/core/os/os2/heap_js.odin deleted file mode 100644 index 15990b517..000000000 --- a/core/os/os2/heap_js.odin +++ /dev/null @@ -1,7 +0,0 @@ -#+build js wasm32, js wasm64p32 -#+private -package os2 - -import "base:runtime" - -_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin deleted file mode 100644 index 1d1f12726..000000000 --- a/core/os/os2/heap_linux.odin +++ /dev/null @@ -1,6 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin deleted file mode 100644 index 1b52aed75..000000000 --- a/core/os/os2/heap_posix.odin +++ /dev/null @@ -1,7 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/os2/heap_wasi.odin b/core/os/os2/heap_wasi.odin deleted file mode 100644 index 7da3c4845..000000000 --- a/core/os/os2/heap_wasi.odin +++ /dev/null @@ -1,6 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin deleted file mode 100644 index 7fd4529a0..000000000 --- a/core/os/os2/heap_windows.odin +++ /dev/null @@ -1,106 +0,0 @@ -#+private -package os2 - -import "core:mem" -import win32 "core:sys/windows" - -heap_alloc :: proc(size: int, zero_memory: bool) -> rawptr { - return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) -} - -heap_resize :: proc(ptr: rawptr, new_size: int, zero_memory: bool) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size, zero_memory) - } - - return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size)) -} -heap_free :: proc(ptr: rawptr) { - if ptr == nil { - return - } - win32.HeapFree(win32.GetProcessHeap(), 0, ptr) -} - -_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // - - aligned_alloc :: proc(size, alignment: int, zero_memory: bool, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr), zero_memory) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, mem.Allocator_Error) { - if p == nil { - return nil, nil - } - return aligned_alloc(new_size, new_alignment, true, p) - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return aligned_alloc(size, alignment, mode == .Alloc) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return aligned_alloc(size, alignment, true) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin deleted file mode 100644 index 9616af8b0..000000000 --- a/core/os/os2/internal_util.odin +++ /dev/null @@ -1,94 +0,0 @@ -#+private -package os2 - -import "base:intrinsics" -import "base:runtime" -import "core:math/rand" - - -// Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix -// parts which are split by the last "*" -@(require_results) -_prefix_and_suffix :: proc(pattern: string) -> (prefix, suffix: string, err: Error) { - for i in 0..= 0; i -= 1 { - if pattern[i] == '*' { - prefix, suffix = pattern[:i], pattern[i+1:] - break - } - } - return -} - -@(require_results) -clone_string :: proc(s: string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { - buf := make([]byte, len(s), allocator) or_return - copy(buf, s) - return string(buf), nil -} - - -@(require_results) -clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstring, err: runtime.Allocator_Error) { - res = "" // do not use a `nil` cstring - buf := make([]byte, len(s)+1, allocator) or_return - copy(buf, s) - buf[len(s)] = 0 - return cstring(&buf[0]), nil -} - -@(require_results) -string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { - s := string(b) - i := 0 - for ; i < len(s); i += 1 { - if s[i] == 0 { - break - } - } - return s[:i] -} - -@(require_results) -concatenate_strings_from_buffer :: proc(buf: []byte, strings: ..string) -> string { - n := 0 - for s in strings { - (n < len(buf)) or_break - n += copy(buf[n:], s) - } - n = min(len(buf), n) - return string(buf[:n]) -} - -@(require_results) -concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { - n := 0 - for s in strings { - n += len(s) - } - buf := make([]byte, n, allocator) or_return - n = 0 - for s in strings { - n += copy(buf[n:], s) - } - return string(buf), nil -} - -@(require_results) -random_string :: proc(buf: []byte) -> string { - for i := 0; i < len(buf); i += 16 { - n := rand.uint64() - end := min(i + 16, len(buf)) - for j := i; j < end; j += 1 { - buf[j] = '0' + u8(n) % 10 - n >>= 4 - } - } - return string(buf) -} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin deleted file mode 100644 index ac18b7562..000000000 --- a/core/os/os2/path.odin +++ /dev/null @@ -1,980 +0,0 @@ -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.") - -/* -Return true if `c` is a character used to separate paths into directory and -file hierarchies on the current system. -*/ -@(require_results) -is_path_separator :: proc(c: byte) -> bool { - return _is_path_separator(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 - -/* -Make a new directory. - -If `path` is relative, it will be relative to the process's current working directory. -*/ -make_directory :: proc(name: string, perm: int = 0o755) -> Error { - return _mkdir(name, perm) -} - -mkdir_all :: make_directory_all - -/* -Make a new directory, creating new intervening directories when needed. - -If `path` is relative, it will be relative to the process's current working directory. -*/ -make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { - return _mkdir_all(path, perm) -} - -/* -Delete `path` and all files and directories inside of `path` if it is a directory. - -If `path` is relative, it will be relative to the process's current working directory. -*/ -remove_all :: proc(path: string) -> Error { - return _remove_all(path) -} - -getwd :: get_working_directory - -/* -Get the working directory of the current process. - -*Allocates Using Provided Allocator* -*/ -@(require_results) -get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _get_working_directory(allocator) -} - -setwd :: set_working_directory - -/* -Change the working directory of the current process. - -*Allocates Using Provided Allocator* -*/ -set_working_directory :: proc(dir: string) -> (err: Error) { - return _set_working_directory(dir) -} - -/* -Get the path for the currently running executable. - -*Allocates Using Provided Allocator* -*/ -@(require_results) -get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - return _get_executable_path(allocator) -} - -/* -Get the directory for the currently running executable. - -*Allocates Using Provided Allocator* -*/ -@(require_results) -get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - path = _get_executable_path(allocator) or_return - path, _ = split_path(path) - return -} - -/* -Compare two paths for exactness without normalization. - -This procedure takes into account case-sensitivity on differing systems. -*/ -@(require_results) -are_paths_identical :: proc(a, b: string) -> (identical: bool) { - return _are_paths_identical(a, b) -} - -/* -Normalize a path. - -*Allocates Using Provided Allocator* - -This will remove duplicate separators and unneeded references to the current or -parent directory. -*/ -@(require_results) -clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) { - if path == "" || path == "." { - return strings.clone(".", allocator) - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - // The extra byte is to simplify appending path elements by letting the - // loop to end each with a separator. We'll trim the last one when we're done. - buffer := make([]u8, len(path) + 1, temp_allocator) or_return - - // This is the only point where Windows and POSIX differ, as Windows has - // alphabet-based volumes for root paths. - rooted, start := _clean_path_handle_start(path, buffer) - - head, buffer_i := start, start - for i, j := start, start; i <= len(path); i += 1 { - if i == len(path) || _is_path_separator(path[i]) { - elem := path[j:i] - j = i + 1 - - switch elem { - case "", ".": - // Skip duplicate path separators and current directory references. - case "..": - if !rooted && buffer_i == head { - // Only allow accessing further parent directories when the path is relative. - buffer[buffer_i] = '.' - buffer[buffer_i+1] = '.' - buffer[buffer_i+2] = _Path_Separator - buffer_i += 3 - head = buffer_i - } else { - // Roll back to the last separator or the head of the buffer. - back_to := head - // `buffer_i` will be equal to 1 + the last set byte, so - // skipping two bytes avoids the final separator we just - // added. - for k := buffer_i-2; k >= head; k -= 1 { - if _is_path_separator(buffer[k]) { - back_to = k + 1 - break - } - } - buffer_i = back_to - } - case: - // Copy the path element verbatim and add a separator. - copy(buffer[buffer_i:], elem) - buffer_i += len(elem) - buffer[buffer_i] = _Path_Separator - buffer_i += 1 - } - } - } - - // Trim the final separator. - // NOTE: No need to check if the last byte is a separator, as we always add it. - if buffer_i > start { - buffer_i -= 1 - } - - if buffer_i == 0 { - return strings.clone(".", allocator) - } - - compact := make([]u8, buffer_i, allocator) or_return - copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here - return string(compact), nil -} - -/* -Return true if `path` is an absolute path as opposed to a relative one. -*/ -@(require_results) -is_absolute_path :: proc(path: string) -> bool { - return _is_absolute_path(path) -} - -/* -Get the absolute path to `path` with respect to the process's current directory. - -*Allocates Using Provided Allocator* -*/ -@(require_results) -get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - return _get_absolute_path(path, allocator) -} - -/* -Get the relative path needed to change directories from `base` to `target`. - -*Allocates Using Provided Allocator* - -The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`. - -NOTE: This procedure expects both `base` and `target` to be normalized first, -which can be done by calling `clean_path` on them if needed. - -This procedure will return an `Invalid_Path` error if `base` begins with a -reference to the parent directory (`".."`). Use `get_working_directory` with -`join_path` to construct absolute paths for both arguments instead. -*/ -@(require_results) -get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) { - if _are_paths_identical(base, target) { - return strings.clone(".", allocator) - } - if base == "." { - return strings.clone(target, allocator) - } - - // This is the first point where Windows and POSIX differ, as Windows has - // alphabet-based volumes for root paths. - if !_get_relative_path_handle_start(base, target) { - return "", .Invalid_Path - } - if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) { - // We could do the work for the user of getting absolute paths for both - // arguments, but that could make something costly (repeatedly - // normalizing paths) convenient, when it would be better for the user - // to store already-finalized paths and operate on those instead. - return "", .Invalid_Path - } - - // This is the other point where Windows and POSIX differ, as Windows is - // case-insensitive. - common := _get_common_path_len(base, target) - - // Get the result of splitting `base` and `target` on _Path_Separator, - // comparing them up to their most common elements, then count how many - // unshared parts are in the split `base`. - seps := 0 - size := 0 - if len(base)-common > 0 { - seps = 1 - size = 2 - } - // This range skips separators on the ends of the string. - for i in common+1.. 0 { - // Account for leading separators on the target after cutting the common part. - // (i.e. base == `/home`, target == `/home/a`) - if _is_path_separator(trailing[0]) { - trailing = trailing[1:] - } - size += len(trailing) - if seps > 0 { - size += 1 - } - } - if trailing == "." { - trailing = "" - size -= 2 - } - - // Build the string. - buf := make([]u8, size, allocator) or_return - n := 0 - if seps > 0 { - buf[0] = '.' - buf[1] = '.' - n = 2 - } - for _ in 1.. 0 { - if seps > 0 { - buf[n] = _Path_Separator - n += 1 - } - copy(buf[n:], trailing) - } - - path = string(buf) - - return -} - -/* -Split a path into a directory hierarchy and a filename. - -For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`. -*/ -@(require_results) -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. - -*Allocates Using Provided Allocator* - -For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`. -*/ -@(require_results) -join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { - for e, i in elems { - if e != "" { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return - return clean_path(p, allocator) - } - } - return "", nil -} - -/* -Split a filename from its extension. - -This procedure splits on the last separator. - -If the filename begins with a separator, such as `".readme.txt"`, the separator -will be included in the filename, resulting in `".readme"` and `"txt"`. - -For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`. -*/ -@(require_results) -split_filename :: proc(filename: string) -> (base, ext: string) { - i := strings.last_index_byte(filename, '.') - if i <= 0 { - return filename, "" - } - return filename[:i], filename[i+1:] -} - -/* -Split a filename from its extension. - -This procedure splits on the first separator. - -If the filename begins with a separator, such as `".readme.txt.gz"`, the separator -will be included in the filename, resulting in `".readme"` and `"txt.gz"`. - -For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`. -*/ -@(require_results) -split_filename_all :: proc(filename: string) -> (base, ext: string) { - i := strings.index_byte(filename, '.') - if i == 0 { - j := strings.index_byte(filename[1:], '.') - if j != -1 { - j += 1 - } - i = j - } - if i == -1 { - return filename, "" - } - return filename[:i], filename[i+1:] -} - -/* -Join `base` and `ext` with the system's filename extension separator. - -*Allocates Using Provided Allocator* - -For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`. -*/ -@(require_results) -join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) { - if len(base) == 0 { - return strings.clone(ext, allocator) - } else if len(ext) == 0 { - return strings.clone(base, allocator) - } - - buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return - copy(buf, base) - buf[len(base)] = '.' - copy(buf[1+len(base):], ext) - - return string(buf), nil -} - -/* -Split a string that is separated by a system-specific separator, typically used -for environment variables specifying multiple directories. - -*Allocates Using Provided Allocator* - -For example, there is the "PATH" environment variable on POSIX systems which -this procedure can split into separate entries. -*/ -@(require_results) -split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: 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 == Path_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 == Path_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 -} - -/* -`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) { - _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) { - // TODO(bill): os.lstat on here to check for error - m := make([]string, 1) - m[0] = pattern - return m[:], nil - } - - // NOTE(Jeroen): For `glob`, we need this version of `split`, which leaves the trailing `/` on `dir`. - dir, file := _split(pattern) - - temp_buf: [8]byte - vol_len: int - vol_len, dir = clean_glob_path(dir, temp_buf[:]) - - if !has_meta(dir[vol_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) { - 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 { - 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 !slash_equal(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) -> (int, 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_darwin.odin b/core/os/os2/path_darwin.odin deleted file mode 100644 index 65aaf1e95..000000000 --- a/core/os/os2/path_darwin.odin +++ /dev/null @@ -1,17 +0,0 @@ -package os2 - -import "base:runtime" - -import "core:sys/darwin" -import "core:sys/posix" - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- - ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer)) - if ret > 0 { - return clone_string(string(buffer[:ret]), allocator) - } - - err = _get_platform_error() - return -} diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin deleted file mode 100644 index e7e4f63c9..000000000 --- a/core/os/os2/path_freebsd.odin +++ /dev/null @@ -1,29 +0,0 @@ -package os2 - -import "base:runtime" - -import "core:sys/freebsd" -import "core:sys/posix" - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)} - - size: uint - if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE { - err = _get_platform_error(posix.Errno(ret)) - return - } - assert(size > 0) - - buf := make([]byte, size, allocator) or_return - defer if err != nil { delete(buf, allocator) } - - assert(uint(len(buf)) == size) - - if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE { - err = _get_platform_error(posix.Errno(ret)) - return - } - - return string(buf[:size-1]), nil -} diff --git a/core/os/os2/path_js.odin b/core/os/os2/path_js.odin deleted file mode 100644 index 0c0d1424b..000000000 --- a/core/os/os2/path_js.odin +++ /dev/null @@ -1,85 +0,0 @@ -#+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/path_linux.odin b/core/os/os2/path_linux.odin deleted file mode 100644 index 1c9927843..000000000 --- a/core/os/os2/path_linux.odin +++ /dev/null @@ -1,227 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:strings" -import "core:strconv" -import "core:sys/linux" - -_Path_Separator :: '/' -_Path_Separator_String :: "/" -_Path_List_Separator :: ':' - -_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC} - -_is_path_separator :: proc(c: byte) -> bool { - return c == _Path_Separator -} - -_mkdir :: proc(path: string, perm: int) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - path_cstr := clone_to_cstring(path, temp_allocator) or_return - return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) -} - -_mkdir_all :: proc(path: string, perm: int) -> Error { - mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { - i: int - for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} - if i == 0 { - return _get_platform_error(linux.close(dfd)) - } - path[i] = 0 - new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) - #partial switch errno { - case .ENOENT: - if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE { - return _get_platform_error(errno) - } - has_created^ = true - if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE { - return _get_platform_error(errno) - } - fallthrough - case .NONE: - if errno = linux.close(dfd); errno != .NONE { - return _get_platform_error(errno) - } - // skip consecutive '/' - for i += 1; i < len(path) && path[i] == '/'; i += 1 {} - return mkdirat(new_dfd, path[i:], perm, has_created) - } - return _get_platform_error(errno) - } - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - // need something we can edit, and use to generate cstrings - path_bytes := make([]u8, len(path) + 1, temp_allocator) - - // zero terminate the byte slice to make it a valid cstring - copy(path_bytes, path) - path_bytes[len(path)] = 0 - - dfd: linux.Fd - errno: linux.Errno - if path_bytes[0] == '/' { - dfd, errno = linux.open("/", _OPENDIR_FLAGS) - path_bytes = path_bytes[1:] - } else { - dfd, errno = linux.open(".", _OPENDIR_FLAGS) - } - if errno != .NONE { - return _get_platform_error(errno) - } - - has_created: bool - mkdirat(dfd, path_bytes, perm, &has_created) or_return - return nil if has_created else .Exist -} - -_remove_all :: proc(path: string) -> Error { - remove_all_dir :: proc(dfd: linux.Fd) -> Error { - n := 64 - buf := make([]u8, n) - defer delete(buf) - - loop: for { - buflen, errno := linux.getdents(dfd, buf[:]) - #partial switch errno { - case .EINVAL: - delete(buf) - n *= 2 - buf = make([]u8, n) - continue loop - case .NONE: - if buflen == 0 { break loop } - case: - return _get_platform_error(errno) - } - - offset: int - for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { - d_name_str := linux.dirent_name(d) - d_name_cstr := strings.unsafe_string_to_cstring(d_name_str) - - /* check for current or parent directory (. or ..) */ - if d_name_str == "." || d_name_str == ".." { - continue - } - - #partial switch d.type { - case .DIR: - new_dfd: linux.Fd - new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS) - if errno != .NONE { - return _get_platform_error(errno) - } - defer linux.close(new_dfd) - remove_all_dir(new_dfd) or_return - errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR}) - case: - errno = linux.unlinkat(dfd, d_name_cstr, nil) - } - - if errno != .NONE { - return _get_platform_error(errno) - } - } - } - return nil - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - path_cstr := clone_to_cstring(path, temp_allocator) or_return - - fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) - #partial switch errno { - case .NONE: - break - case .ENOTDIR: - return _get_platform_error(linux.unlink(path_cstr)) - case: - return _get_platform_error(errno) - } - - defer linux.close(fd) - remove_all_dir(fd) or_return - return _get_platform_error(linux.rmdir(path_cstr)) -} - -_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { - // NOTE(tetra): I would use PATH_MAX here, but I was not able to find - // an authoritative value for it across all systems. - // The largest value I could find was 4096, so might as well use the page size. - // NOTE(jason): Avoiding libc, so just use 4096 directly - PATH_MAX :: 4096 - buf := make([dynamic]u8, PATH_MAX, allocator) - for { - #no_bounds_check n, errno := linux.getcwd(buf[:]) - if errno == .NONE { - return string(buf[:n-1]), nil - } - if errno != .ERANGE { - return "", _get_platform_error(errno) - } - resize(&buf, len(buf)+PATH_MAX) - } - unreachable() -} - -_set_working_directory :: proc(dir: string) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - dir_cstr := clone_to_cstring(dir, temp_allocator) or_return - return _get_platform_error(linux.chdir(dir_cstr)) -} - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := make([dynamic]byte, 1024, temp_allocator) or_return - for { - n, errno := linux.readlink("/proc/self/exe", buf[:]) - if errno != .NONE { - err = _get_platform_error(errno) - return - } - - if n < len(buf) { - return clone_string(string(buf[:n]), allocator) - } - - resize(&buf, len(buf)*2) or_return - } -} - -_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { - PROC_FD_PATH :: "/proc/self/fd/" - - buf: [32]u8 - copy(buf[:], PROC_FD_PATH) - - strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10) - - if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { - delete(fullpath, allocator) - fullpath = "" - } - return -} - -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - rel := path - if rel == "" { - rel = "." - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {}) - if errno != nil { - err = _get_platform_error(errno) - return - } - defer linux.close(fd) - - return _get_full_path(fd, allocator) -} diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin deleted file mode 100644 index 815102dea..000000000 --- a/core/os/os2/path_netbsd.odin +++ /dev/null @@ -1,24 +0,0 @@ -package os2 - -import "base:runtime" - -import "core:sys/posix" - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := make([dynamic]byte, 1024, temp_allocator) or_return - for { - n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) - if n < 0 { - err = _get_platform_error() - return - } - - if n < len(buf) { - return clone_string(string(buf[:n]), allocator) - } - - resize(&buf, len(buf)*2) or_return - } -} diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin deleted file mode 100644 index cbc0346d4..000000000 --- a/core/os/os2/path_openbsd.odin +++ /dev/null @@ -1,57 +0,0 @@ -package os2 - -import "base:runtime" - -import "core:strings" -import "core:sys/posix" - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - // OpenBSD does not have an API for this, we do our best below. - - if len(runtime.args__) <= 0 { - err = .Invalid_Path - return - } - - real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) { - real := posix.realpath(path) - if real == nil { - err = _get_platform_error() - return - } - defer posix.free(real) - return clone_string(string(real), allocator) - } - - arg := runtime.args__[0] - sarg := string(arg) - - if len(sarg) == 0 { - err = .Invalid_Path - return - } - - if sarg[0] == '.' || sarg[0] == '/' { - return real(arg, allocator) - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := strings.builder_make(temp_allocator) - - paths := get_env("PATH", temp_allocator) - for dir in strings.split_iterator(&paths, ":") { - strings.builder_reset(&buf) - strings.write_string(&buf, dir) - strings.write_string(&buf, "/") - strings.write_string(&buf, sarg) - - cpath := strings.to_cstring(&buf) or_return - if posix.access(cpath, {.X_OK}) == .OK { - return real(cpath, allocator) - } - } - - err = .Invalid_Path - return -} diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin deleted file mode 100644 index 173cb6b6d..000000000 --- a/core/os/os2/path_posix.odin +++ /dev/null @@ -1,142 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -import "core:sys/posix" - -_Path_Separator :: '/' -_Path_Separator_String :: "/" -_Path_List_Separator :: ':' - -_is_path_separator :: proc(c: byte) -> bool { - return c == _Path_Separator -} - -_mkdir :: proc(name: string, perm: int) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cname := clone_to_cstring(name, temp_allocator) or_return - if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { - return _get_platform_error() - } - return nil -} - -_mkdir_all :: proc(path: string, perm: int) -> Error { - if path == "" { - return .Invalid_Path - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - if exists(path) { - return .Exist - } - - clean_path := clean_path(path, temp_allocator) or_return - return internal_mkdir_all(clean_path, perm) - - internal_mkdir_all :: proc(path: string, perm: int) -> Error { - dir, file := split_path(path) - if file != path && dir != "/" { - if len(dir) > 1 && dir[len(dir) - 1] == '/' { - dir = dir[:len(dir) - 1] - } - internal_mkdir_all(dir, perm) or_return - } - - err := _mkdir(path, perm) - if err == .Exist { err = nil } - return err - } -} - -_remove_all :: proc(path: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cpath := clone_to_cstring(path, temp_allocator) or_return - - dir := posix.opendir(cpath) - if dir == nil { - return _get_platform_error() - } - defer posix.closedir(dir) - - for { - posix.set_errno(.NONE) - entry := posix.readdir(dir) - if entry == nil { - if errno := posix.errno(); errno != .NONE { - return _get_platform_error() - } else { - break - } - } - - cname := cstring(raw_data(entry.d_name[:])) - if cname == "." || cname == ".." { - continue - } - - fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator) - if entry.d_type == .DIR { - _remove_all(fullpath[:len(fullpath)-1]) or_return - } else { - if posix.unlink(cstring(raw_data(fullpath))) != .OK { - return _get_platform_error() - } - } - } - - if posix.rmdir(cpath) != .OK { - return _get_platform_error() - } - return nil -} - -_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf: [dynamic]byte - buf.allocator = temp_allocator - size := uint(posix.PATH_MAX) - - cwd: cstring - for ; cwd == nil; size *= 2 { - resize(&buf, size) - - cwd = posix.getcwd(raw_data(buf), len(buf)) - if cwd == nil && posix.errno() != .ERANGE { - err = _get_platform_error() - return - } - } - - return clone_string(string(cwd), allocator) -} - -_set_working_directory :: proc(dir: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - cdir := clone_to_cstring(dir, temp_allocator) or_return - if posix.chdir(cdir) != .OK { - err = _get_platform_error() - } - return -} - -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - rel := path - if rel == "" { - rel = "." - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - rel_cstr := clone_to_cstring(rel, temp_allocator) or_return - path_ptr := posix.realpath(rel_cstr, nil) - if path_ptr == nil { - return "", _get_platform_error() - } - defer posix.free(path_ptr) - - path_str := clone_string(string(path_ptr), allocator) or_return - return path_str, nil -} diff --git a/core/os/os2/path_posixfs.odin b/core/os/os2/path_posixfs.odin deleted file mode 100644 index 0736e73d1..000000000 --- a/core/os/os2/path_posixfs.odin +++ /dev/null @@ -1,57 +0,0 @@ -#+private -#+build linux, darwin, netbsd, freebsd, openbsd, wasi -package os2 - -// This implementation is for all systems that have POSIX-compliant filesystem paths. - -_are_paths_identical :: proc(a, b: string) -> (identical: bool) { - return a == b -} - -_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { - // Preserve rooted paths. - if _is_path_separator(path[0]) { - rooted = true - buffer[0] = _Path_Separator - start = 1 - } - return -} - -_is_absolute_path :: proc(path: string) -> bool { - return len(path) > 0 && _is_path_separator(path[0]) -} - -_get_relative_path_handle_start :: proc(base, target: string) -> bool { - base_rooted := len(base) > 0 && _is_path_separator(base[0]) - target_rooted := len(target) > 0 && _is_path_separator(target[0]) - return base_rooted == target_rooted -} - -_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 -} diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin deleted file mode 100644 index f26e16158..000000000 --- a/core/os/os2/path_wasi.odin +++ /dev/null @@ -1,120 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:sync" -import "core:sys/wasm/wasi" - -_Path_Separator :: '/' -_Path_Separator_String :: "/" -_Path_List_Separator :: ':' - -_is_path_separator :: proc(c: byte) -> bool { - return c == _Path_Separator -} - -_mkdir :: proc(name: string, perm: int) -> Error { - dir_fd, relative, ok := match_preopen(name) - if !ok { - return .Invalid_Path - } - - return _get_platform_error(wasi.path_create_directory(dir_fd, relative)) -} - -_mkdir_all :: proc(path: string, perm: int) -> Error { - if path == "" { - return .Invalid_Path - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - if exists(path) { - return .Exist - } - - clean_path := clean_path(path, temp_allocator) or_return - return internal_mkdir_all(clean_path) - - internal_mkdir_all :: proc(path: string) -> Error { - dir, file := split_path(path) - if file != path && dir != "/" { - if len(dir) > 1 && dir[len(dir) - 1] == '/' { - dir = dir[:len(dir) - 1] - } - internal_mkdir_all(dir) or_return - } - - err := _mkdir(path, 0) - if err == .Exist { err = nil } - return err - } -} - -_remove_all :: proc(path: string) -> (err: Error) { - // PERF: this works, but wastes a bunch of memory using the read_directory_iterator API - // and using open instead of wasi fds directly. - { - dir := open(path) or_return - defer close(dir) - - iter := read_directory_iterator_create(dir) - defer read_directory_iterator_destroy(&iter) - - for fi in read_directory_iterator(&iter) { - _ = read_directory_iterator_error(&iter) or_break - - if fi.type == .Directory { - _remove_all(fi.fullpath) or_return - } else { - remove(fi.fullpath) or_return - } - } - - _ = read_directory_iterator_error(&iter) or_return - } - - return remove(path) -} - -g_wd: string -g_wd_mutex: sync.Mutex - -_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - sync.guard(&g_wd_mutex) - - return clone_string(g_wd if g_wd != "" else "/", allocator) -} - -_set_working_directory :: proc(dir: string) -> (err: Error) { - sync.guard(&g_wd_mutex) - - if dir == g_wd { - return - } - - if g_wd != "" { - delete(g_wd, file_allocator()) - } - - g_wd = clone_string(dir, file_allocator()) or_return - return -} - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - if len(args) <= 0 { - return clone_string("/", allocator) - } - - arg := args[0] - if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') { - return clone_string(arg, allocator) - } - - return concatenate({"/", arg}, allocator) -} - -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - return "", .Unsupported -} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin deleted file mode 100644 index 275fe3e18..000000000 --- a/core/os/os2/path_windows.odin +++ /dev/null @@ -1,359 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import "core:strings" -import win32 "core:sys/windows" - -_Path_Separator :: '\\' -_Path_Separator_String :: "\\" -_Path_List_Separator :: ';' - -_is_path_separator :: proc(c: byte) -> bool { - return c == '\\' || c == '/' -} - -_mkdir :: proc(name: string, perm: int) -> Error { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) { - return _get_platform_error() - } - return nil -} - -_mkdir_all :: proc(path: string, perm: int) -> Error { - fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { - if len(p) == len(`\\?\c:`) { - if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { - s = concatenate({p, `\`}, file_allocator()) or_return - allocated = true - return - } - } - return p, false, nil - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - dir_stat, err := stat(path, temp_allocator) - if err == nil { - if dir_stat.type == .Directory { - return nil - } - return .Exist - } - - i := len(path) - for i > 0 && is_path_separator(path[i-1]) { - i -= 1 - } - - j := i - for j > 0 && !is_path_separator(path[j-1]) { - j -= 1 - } - - if j > 1 { - new_path, allocated := fix_root_directory(path[:j-1]) or_return - defer if allocated { - delete(new_path, file_allocator()) - } - mkdir_all(new_path, perm) or_return - } - - err = mkdir(path, perm) - if err != nil { - new_dir_stat, err1 := lstat(path, temp_allocator) - if err1 == nil && new_dir_stat.type == .Directory { - return nil - } - return err - } - return nil -} - -_remove_all :: proc(path: string) -> Error { - if path == "" { - return nil - } - - err := remove(path) - if err == nil || err == .Not_Exist { - return nil - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - dir := win32_utf8_to_wstring(path, temp_allocator) or_return - - empty: [1]u16 - - file_op := win32.SHFILEOPSTRUCTW { - nil, - win32.FO_DELETE, - dir, - cstring16(&empty[0]), - win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, - false, - nil, - cstring16(&empty[0]), - } - res := win32.SHFileOperationW(&file_op) - if res != 0 { - return _get_platform_error() - } - return nil -} - -@private cwd_lock: win32.SRWLOCK // zero is initialized - -_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - win32.AcquireSRWLockExclusive(&cwd_lock) - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return - - sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) - assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return win32_utf16_to_utf8(dir_buf_wstr, allocator) -} - -_set_working_directory :: proc(dir: string) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return - - win32.AcquireSRWLockExclusive(&cwd_lock) - - if !win32.SetCurrentDirectoryW(wstr) { - err = _get_platform_error() - } - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return -} - -_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buf := make([dynamic]u16, 512, temp_allocator) or_return - for { - ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) - if ret == 0 { - err = _get_platform_error() - return - } - - if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER { - resize(&buf, len(buf)*2) or_return - continue - } - - return win32_utf16_to_utf8(buf[:ret], allocator) - } -} - -@(private) -can_use_long_paths: bool - -@(init) -init_long_path_support :: proc "contextless" () { - can_use_long_paths = false - - key: win32.HKEY - res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key) - defer win32.RegCloseKey(key) - if res != 0 { - return - } - - value: u32 - size := u32(size_of(value)) - res = win32.RegGetValueW( - key, - nil, - win32.L("LongPathsEnabled"), - win32.RRF_RT_ANY, - nil, - &value, - &size, - ) - if res != 0 { - return - } - if value == 1 { - can_use_long_paths = true - } -} - -@(require_results) -_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) { - return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator) -} - -@(require_results) -_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) { - return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator) -} - -@(require_results) -_fix_long_path_internal :: proc(path: string) -> string { - if can_use_long_paths { - return path - } - - // When using win32 to create a directory, the path - // cannot be too long that you cannot append an 8.3 - // file name, because MAX_PATH is 260, 260-12 = 248 - if len(path) < 248 { - return path - } - - // UNC paths do not need to be modified - if len(path) >= 2 && path[:2] == `\\` { - return path - } - - if !_is_absolute_path(path) { // relative path - return path - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - PREFIX :: `\\?` - path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator) - copy(path_buf, PREFIX) - n := len(path) - r, w := 0, len(PREFIX) - for r < n { - switch { - case is_path_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): - // \.\ - r += 1 - case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): - // Skip \..\ paths - return path - case: - path_buf[w] = '\\' - w += 1 - for r < n && !is_path_separator(path[r]) { - path_buf[w] = path[r] - r += 1 - w += 1 - } - } - } - - // Root directories require a trailing \ - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 - } - - return string(path_buf[:w]) -} - -_are_paths_identical :: strings.equal_fold - -_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { - // Preserve rooted paths. - start = _volume_name_len(path) - if start > 0 { - rooted = true - if len(path) > start && _is_path_separator(path[start]) { - // Take `C:` to `C:\`. - start += 1 - } - copy(buffer, path[:start]) - for n in 0.. bool { - if _is_reserved_name(path) { - return true - } - if len(path) > 0 && _is_path_separator(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_path_separator(path[0]) -} - -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - rel := path - if rel == "" { - rel = "." - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator) - n := win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), 0, nil, nil) - if n == 0 { - return "", _get_platform_error() - } - - buf := make([]u16, n, temp_allocator) or_return - n = win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), u32(n), cstring16(raw_data(buf)), nil) - if n == 0 { - return "", _get_platform_error() - } - - return win32.utf16_to_utf8(buf, allocator) -} - -_get_relative_path_handle_start :: proc(base, target: string) -> bool { - base_root := base[:_volume_name_len(base)] - target_root := target[:_volume_name_len(target)] - return strings.equal_fold(base_root, target_root) -} - -_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 strings.equal_fold(base[i:j], target[i:j]) { - i = j - } else { - break - } - } - } - return i -} - -_split_path :: proc(path: string) -> (dir, file: string) { - vol_len := _volume_name_len(path) - - i := len(path) - 1 - for i >= vol_len && !_is_path_separator(path[i]) { - i -= 1 - } - if i == vol_len { - return path[:i+1], path[i+1:] - } else if i > vol_len { - return path[:i], path[i+1:] - } - return "", path -} \ No newline at end of file diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin deleted file mode 100644 index 5d3e8368e..000000000 --- a/core/os/os2/pipe.odin +++ /dev/null @@ -1,43 +0,0 @@ -package os2 - -/* -Create an anonymous pipe. - -This procedure creates an anonymous pipe, returning two ends of the pipe, `r` -and `w`. The file `r` is the readable end of the pipe. The file `w` is a -writeable end of the pipe. - -Pipes are used as an inter-process communication mechanism, to communicate -between a parent and a child process. The child uses one end of the pipe to -write data, and the parent uses the other end to read from the pipe -(or vice-versa). When a parent passes one of the ends of the pipe to the child -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 -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. -*/ -@(require_results) -pipe :: proc() -> (r, w: ^File, err: Error) { - return _pipe() -} - -/* -Check if the pipe has any data. - -This procedure checks whether a read-end of the pipe has data that can be -read, and returns `true`, if the pipe has readable data, and `false` if the -pipe is empty. This procedure does not block the execution of the current -thread. - -**Note**: If the other end of the pipe was closed by the child process, the -`.Broken_Pipe` -can be returned by this procedure. Handle these errors accordingly. -*/ -@(require_results) -pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { - return _pipe_has_data(r) -} diff --git a/core/os/os2/pipe_js.odin b/core/os/os2/pipe_js.odin deleted file mode 100644 index 253228f86..000000000 --- a/core/os/os2/pipe_js.odin +++ /dev/null @@ -1,14 +0,0 @@ -#+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/pipe_linux.odin b/core/os/os2/pipe_linux.odin deleted file mode 100644 index bb4456e1c..000000000 --- a/core/os/os2/pipe_linux.odin +++ /dev/null @@ -1,43 +0,0 @@ -#+private -package os2 - -import "core:sys/linux" - -_pipe :: proc() -> (r, w: ^File, err: Error) { - fds: [2]linux.Fd - errno := linux.pipe2(&fds, {.CLOEXEC}) - if errno != .NONE { - return nil, nil,_get_platform_error(errno) - } - - r = _new_file(uintptr(fds[0]), "", file_allocator()) or_return - w = _new_file(uintptr(fds[1]), "", file_allocator()) or_return - - return -} - -@(require_results) -_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { - if r == nil || r.impl == nil { - return false, nil - } - fd := linux.Fd((^File_Impl)(r.impl).fd) - poll_fds := []linux.Poll_Fd { - linux.Poll_Fd { - fd = fd, - events = {.IN, .HUP}, - }, - } - n, errno := linux.poll(poll_fds, 0) - if n != 1 || errno != nil { - return false, _get_platform_error(errno) - } - pipe_events := poll_fds[0].revents - if pipe_events >= {.IN} { - return true, nil - } - if pipe_events >= {.HUP} { - return false, .Broken_Pipe - } - return false, nil -} \ No newline at end of file diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin deleted file mode 100644 index 7c07bc068..000000000 --- a/core/os/os2/pipe_posix.odin +++ /dev/null @@ -1,73 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "core:sys/posix" -import "core:strings" - -_pipe :: proc() -> (r, w: ^File, err: Error) { - fds: [2]posix.FD - if posix.pipe(&fds) != .OK { - err = _get_platform_error() - return - } - - if posix.fcntl(fds[0], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { - err = _get_platform_error() - return - } - if posix.fcntl(fds[1], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { - err = _get_platform_error() - return - } - - r = __new_file(fds[0], file_allocator()) - ri := (^File_Impl)(r.impl) - - rname := strings.builder_make(file_allocator()) - // TODO(laytan): is this on all the posix targets? - strings.write_string(&rname, "/dev/fd/") - strings.write_int(&rname, int(fds[0])) - ri.name = strings.to_string(rname) - ri.cname = strings.to_cstring(&rname) or_return - - w = __new_file(fds[1], file_allocator()) - wi := (^File_Impl)(w.impl) - - wname := strings.builder_make(file_allocator()) - // TODO(laytan): is this on all the posix targets? - strings.write_string(&wname, "/dev/fd/") - strings.write_int(&wname, int(fds[1])) - wi.name = strings.to_string(wname) - wi.cname = strings.to_cstring(&wname) or_return - - return -} - -@(require_results) -_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { - if r == nil || r.impl == nil { - return false, nil - } - fd := __fd(r) - poll_fds := []posix.pollfd { - posix.pollfd { - fd = fd, - events = {.IN, .HUP}, - }, - } - n := posix.poll(raw_data(poll_fds), u32(len(poll_fds)), 0) - if n < 0 { - return false, _get_platform_error() - } else if n != 1 { - return false, nil - } - pipe_events := poll_fds[0].revents - if pipe_events >= {.IN} { - return true, nil - } - if pipe_events >= {.HUP} { - return false, .Broken_Pipe - } - return false, nil -} diff --git a/core/os/os2/pipe_wasi.odin b/core/os/os2/pipe_wasi.odin deleted file mode 100644 index 19c11b51d..000000000 --- a/core/os/os2/pipe_wasi.odin +++ /dev/null @@ -1,13 +0,0 @@ -#+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/pipe_windows.odin b/core/os/os2/pipe_windows.odin deleted file mode 100644 index d6dc47c9c..000000000 --- a/core/os/os2/pipe_windows.odin +++ /dev/null @@ -1,29 +0,0 @@ -#+private -package os2 - -import win32 "core:sys/windows" - -_pipe :: proc() -> (r, w: ^File, err: Error) { - p: [2]win32.HANDLE - sa := win32.SECURITY_ATTRIBUTES { - nLength = size_of(win32.SECURITY_ATTRIBUTES), - bInheritHandle = true, - } - if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { - return nil, nil, _get_platform_error() - } - return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil -} - -@(require_results) -_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { - if r == nil || r.impl == nil { - return false, nil - } - handle := win32.HANDLE((^File_Impl)(r.impl).fd) - bytes_available: u32 - if !win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { - return false, _get_platform_error() - } - return bytes_available > 0, nil -} \ No newline at end of file diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin deleted file mode 100644 index e4fecf2a5..000000000 --- a/core/os/os2/process.odin +++ /dev/null @@ -1,548 +0,0 @@ -package os2 - -import "base:runtime" - -import "core:time" - -/* -In procedures that explicitly state this as one of the allowed values, -specifies an infinite timeout. -*/ -TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity - -/* -Arguments to the current process. -*/ -args := get_args() - -@(private="file") -internal_args_to_free: []string - -@(private="file") -get_args :: proc "contextless" () -> []string { - context = runtime.default_context() - result := make([]string, len(runtime.args__), heap_allocator()) - for rt_arg, i in runtime.args__ { - result[i] = string(rt_arg) - } - internal_args_to_free = result - return result -} - -@(fini, private="file") -delete_args :: proc "contextless" () { - if internal_args_to_free != nil { - context = runtime.default_context() - delete(internal_args_to_free, heap_allocator()) - } -} - -/* -Exit the current process. -*/ -exit :: proc "contextless" (code: int) -> ! { - runtime.exit(code) -} - -/* -Obtain the UID of the current process. - -**Note(windows)**: Windows doesn't follow the posix permissions model, so -the function simply returns -1. -*/ -@(require_results) -get_uid :: proc() -> int { - return _get_uid() -} - -/* -Obtain the effective UID of the current process. - -The effective UID is typically the same as the UID of the process. In case -the process was run by a user with elevated permissions, the process may -lower the privilege to perform some tasks without privilege. In these cases -the real UID of the process and the effective UID are different. - -**Note(windows)**: Windows doesn't follow the posix permissions model, so -the function simply returns -1. -*/ -@(require_results) -get_euid :: proc() -> int { - return _get_euid() -} - -/* -Obtain the GID of the current process. - -**Note(windows)**: Windows doesn't follow the posix permissions model, so -the function simply returns -1. -*/ -@(require_results) -get_gid :: proc() -> int { - return _get_gid() -} - -/* -Obtain the effective GID of the current process. - -The effective GID is typically the same as the GID of the process. In case -the process was run by a user with elevated permissions, the process may -lower the privilege to perform some tasks without privilege. In these cases -the real GID of the process and the effective GID are different. - -**Note(windows)**: Windows doesn't follow the posix permissions model, so -the function simply returns -1. -*/ -@(require_results) -get_egid :: proc() -> int { - return _get_egid() -} - -/* -Obtain the ID of the current process. -*/ -@(require_results) -get_pid :: proc() -> int { - return _get_pid() -} - -/* -Obtain the ID of the parent process. - -**Note(windows)**: Windows does not mantain strong relationships between -parent and child processes. This function returns the ID of the process -that has created the current process. In case the parent has died, the ID -returned by this function can identify a non-existent or a different -process. -*/ -@(require_results) -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. -*/ -@(require_results) -process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { - return _process_list(allocator) -} - -/* -Bit set specifying which fields of the `Process_Info` struct need to be -obtained by the `process_info()` procedure. Each bit corresponds to a -field in the `Process_Info` struct. -*/ -Process_Info_Fields :: bit_set[Process_Info_Field] -Process_Info_Field :: enum { - Executable_Path, - PPid, - Priority, - Command_Line, - Command_Args, - Environment, - Username, - Working_Dir, -} - -ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} - -/* -Contains information about the process as obtained by the `process_info()` -procedure. -*/ -Process_Info :: struct { - // The information about a process the struct contains. `pid` is always - // stored, no matter what. - fields: Process_Info_Fields, - // The ID of the process. - pid: int, - // The ID of the parent process. - ppid: int, - // The process priority. - priority: int, - // The path to the executable, which the process runs. - executable_path: string, - // The command line supplied to the process. - command_line: string, - // The arguments supplied to the process. - command_args: []string, - // The environment of the process. - environment: []string, - // The username of the user who started the process. - username: string, - // The current working directory of the process. - working_dir: string, -} - -/* -Obtain information about a process. - -This procedure obtains an information, specified by `selection` parameter of -a process given by `pid`. - -Use `free_process_info` to free the memory allocated by this procedure. The -`free_process_info` procedure needs to be called, even if this procedure -returned an error, as some of the fields may have been allocated. - -**Note**: The resulting information may or may contain the fields specified -by the `selection` parameter. Always check whether the returned -`Process_Info` struct has the required fields before checking the error code -returned by this procedure. -*/ -@(require_results) -process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { - return _process_info_by_pid(pid, selection, allocator) -} - -/* -Obtain information about a process. - -This procedure obtains information, specified by `selection` parameter -about a process that has been opened by the application, specified in -the `process` parameter. - -Use `free_process_info` to free the memory allocated by this procedure. The -`free_process_info` procedure needs to be called, even if this procedure -returned an error, as some of the fields may have been allocated. - -**Note**: The resulting information may or may contain the fields specified -by the `selection` parameter. Always check whether the returned -`Process_Info` struct has the required fields before checking the error code -returned by this procedure. -*/ -@(require_results) -process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { - return _process_info_by_handle(process, selection, allocator) -} - -/* -Obtain information about the current process. - -This procedure obtains the information, specified by `selection` parameter -about the currently running process. - -Use `free_process_info` to free the memory allocated by this procedure. The -`free_process_info` procedure needs to be called, even if this procedure -returned an error, as some of the fields may have been allocated. - -**Note**: The resulting information may or may contain the fields specified -by the `selection` parameter. Always check whether the returned -`Process_Info` struct has the required fields before checking the error code -returned by this procedure. -*/ -@(require_results) -current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { - return _current_process_info(selection, allocator) -} - -/* -Obtain information about the specified process. -*/ -process_info :: proc { - process_info_by_pid, - process_info_by_handle, - current_process_info, -} - -/* -Free the information about the process. - -This procedure frees the memory occupied by process info using the provided -allocator. The allocator needs to be the same allocator that was supplied -to the `process_info` function. -*/ -free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { - delete(pi.executable_path, allocator) - delete(pi.command_line, allocator) - for a in pi.command_args { - delete(a, allocator) - } - delete(pi.command_args, allocator) - for s in pi.environment { - delete(s, allocator) - } - delete(pi.environment, allocator) - delete(pi.working_dir, allocator) - delete(pi.username, allocator) -} - -/* -Represents a process handle. - -When a process dies, the OS is free to re-use the pid of that process. The -`Process` struct represents a handle to the process that will refer to a -specific process, even after it has died. - -**Note(linux)**: The `handle` will be referring to pidfd. -*/ -Process :: struct { - pid: int, - handle: uintptr, -} - -Process_Open_Flags :: bit_set[Process_Open_Flag] -Process_Open_Flag :: enum { - // Request for reading from the virtual memory of another process. - Mem_Read, - // Request for writing to the virtual memory of another process. - Mem_Write, -} - -/* -Open a process handle using it's pid. - -This procedure obtains a process handle of a process specified by `pid`. -This procedure can be subject to race conditions. See the description of -`Process`. - -Use `process_close()` function to close the process handle. -*/ -@(require_results) -process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { - return _process_open(pid, flags) -} - -/* - The description of how a process should be created. -*/ -Process_Desc :: struct { - // The working directory of the process. If the string has length 0, the - // working directory is assumed to be the current working directory of the - // current process. - working_dir: string, - // The command to run. Each element of the slice is a separate argument to - // the process. The first element of the slice would be the executable. - command: []string, - // A slice of strings, each having the format `KEY=VALUE` representing the - // full environment that the child process will receive. - // In case this slice is `nil`, the current process' environment is used. - // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env. - env: []string, - // The `stderr` handle to give to the child process. It can be either a file - // or a writeable end of a pipe. Passing `nil` will shut down the process' - // stderr output. - stderr: ^File, - // The `stdout` handle to give to the child process. It can be either a file - // or a writeabe end of a pipe. Passing a `nil` will shut down the process' - // stdout output. - stdout: ^File, - // The `stdin` handle to give to the child process. It can either be a file - // or a readable end of a pipe. Passing a `nil` will shut down the process' - // input. - stdin: ^File, -} - -/* -Create a new process and obtain its handle. - -This procedure creates a new process, with a given command and environment -strings as parameters. Use `environ()` to inherit the environment of the -current process. - -The `desc` parameter specifies the description of how the process should -be created. It contains information such as the command line, the -environment of the process, the starting directory and many other options. -Most of the fields in the struct can be set to `nil` or an empty value. - -Use `process_close` to close the handle to the process. Note, that this -is not the same as terminating the process. One can terminate the process -and not close the handle, in which case the handle would be leaked. In case -the function returns an error, an invalid handle is returned. - -This procedure is not thread-safe. It may alter the inheritance properties -of file handles in an unpredictable manner. In case multiple threads change -handle inheritance properties, make sure to serialize all those calls. -*/ -@(require_results) -process_start :: proc(desc: Process_Desc) -> (Process, Error) { - return _process_start(desc) -} - -/* -Execute the process and capture stdout and stderr streams. - -This procedure creates a new process, with a given command and environment -strings as parameters, and waits until the process finishes execution. While -the process is running, this procedure accumulates the output of its stdout -and stderr streams and returns byte slices containing the captured data from -the streams. - -This procedure expects that `stdout` and `stderr` fields of the `desc` parameter -are left at default, i.e. a `nil` value. You can not capture stdout/stderr and -redirect it to a file at the same time. - -This procedure does not free `stdout` and `stderr` slices before an error is -returned. Make sure to call `delete` on these slices. -*/ -@(require_results) -process_exec :: proc( - desc: Process_Desc, - allocator: runtime.Allocator, - loc := #caller_location, -) -> ( - state: Process_State, - stdout: []byte, - stderr: []byte, - err: Error, -) { - assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc) - assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc) - - stdout_r, stdout_w := pipe() or_return - defer close(stdout_r) - stderr_r, stderr_w := pipe() or_return - defer close(stderr_r) - - process: Process - { - // NOTE(flysand): Make sure the write-ends are closed, regardless - // of the outcome. This makes read-ends readable on our side. - defer close(stdout_w) - defer close(stderr_w) - desc := desc - desc.stdout = stdout_w - desc.stderr = stderr_w - process = process_start(desc) or_return - } - - { - stdout_b: [dynamic]byte - stdout_b.allocator = allocator - - stderr_b: [dynamic]byte - stderr_b.allocator = allocator - - buf: [1024]u8 = --- - - stdout_done, stderr_done, has_data: bool - for err == nil && (!stdout_done || !stderr_done) { - n := 0 - - if !stdout_done { - has_data, err = pipe_has_data(stdout_r) - if has_data { - n, err = read(stdout_r, buf[:]) - } - - switch err { - case nil: - _, err = append(&stdout_b, ..buf[:n]) - case .EOF, .Broken_Pipe: - stdout_done = true - err = nil - } - } - - if err == nil && !stderr_done { - n = 0 - has_data, err = pipe_has_data(stderr_r) - if has_data { - n, err = read(stderr_r, buf[:]) - } - - switch err { - case nil: - _, err = append(&stderr_b, ..buf[:n]) - case .EOF, .Broken_Pipe: - stderr_done = true - err = nil - } - } - } - - stdout = stdout_b[:] - stderr = stderr_b[:] - } - - if err != nil { - state, _ = process_wait(process, timeout=0) - if !state.exited { - _ = process_kill(process) - state, _ = process_wait(process) - } - return - } - - state, err = process_wait(process) - return -} - -/* - The state of the process after it has finished execution. -*/ -Process_State :: struct { - // The ID of the process. - pid: int, - // Specifies whether the process has terminated or is still running. - exited: bool, - // The exit code of the process, if it has exited. - // Will also store the number of the exception or signal that has crashed the - // process. - exit_code: int, - // Specifies whether the termination of the process was successfull or not, - // i.e. whether it has crashed or not. - // **Note(windows)**: On windows `true` is always returned, as there is no - // reliable way to obtain information about whether the process has crashed. - success: bool, - // The time the process has spend executing in kernel time. - system_time: time.Duration, - // The time the process has spend executing in userspace. - user_time: time.Duration, -} - -/* -Wait for a process event. - -This procedure blocks the execution until the process has exited or the -timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, -no timeout restriction is imposed and the procedure can block indefinately. - -If the timeout has expired, the `General_Error.Timeout` is returned as -the error. - -If an error is returned for any other reason, other than timeout, the -process state is considered undetermined. -*/ -@(require_results) -process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { - return _process_wait(process, timeout) -} - -/* -Close the handle to a process. - -This procedure closes the handle associated with a process. It **does not** -terminate a process, in case it was running. In case a termination is -desired, kill the process first, wait for the process to finish, -then close the handle. -*/ -@(require_results) -process_close :: proc(process: Process) -> (Error) { - return _process_close(process) -} - -/* -Terminate a process. - -This procedure terminates a process, specified by it's handle, `process`. -*/ -@(require_results) -process_kill :: proc(process: Process) -> (Error) { - return _process_kill(process) -} diff --git a/core/os/os2/process_freebsd.odin b/core/os/os2/process_freebsd.odin deleted file mode 100644 index 8a31eb62c..000000000 --- a/core/os/os2/process_freebsd.odin +++ /dev/null @@ -1,36 +0,0 @@ -#+private -#+build freebsd -package os2 - -import "core:c" - -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 deleted file mode 100644 index a59a79d45..000000000 --- a/core/os/os2/process_js.odin +++ /dev/null @@ -1,95 +0,0 @@ -#+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 -} - -_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 -} - -_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/process_linux.odin b/core/os/os2/process_linux.odin deleted file mode 100644 index 4afd9f3fc..000000000 --- a/core/os/os2/process_linux.odin +++ /dev/null @@ -1,868 +0,0 @@ -#+build linux -#+private file -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") -_get_uid :: proc() -> int { - return int(linux.getuid()) -} - -@(private="package") -_get_euid :: proc() -> int { - return int(linux.geteuid()) -} - -@(private="package") -_get_gid :: proc() -> int { - return int(linux.getgid()) -} - -@(private="package") -_get_egid :: proc() -> int { - return int(linux.getegid()) -} - -@(private="package") -_get_pid :: proc() -> int { - return int(linux.getpid()) -} - -@(private="package") -_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 }) - - dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) - #partial switch errno { - case .NONE: - // okay - case .ENOTDIR: - err = .Invalid_Dir - return - case .ENOENT: - err = .Not_Exist - return - case: - err = _get_platform_error(errno) - return - } - defer linux.close(dir_fd) - - dynamic_list := make([dynamic]int, temp_allocator) or_return - - buf := make([dynamic]u8, 128, 128, temp_allocator) or_return - loop: for { - buflen: int - buflen, errno = linux.getdents(dir_fd, buf[:]) - #partial switch errno { - case .EINVAL: - resize(&buf, len(buf) * 2) - continue loop - case .NONE: - if buflen == 0 { break loop } - case: - return {}, _get_platform_error(errno) - } - - offset: int - for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { - d_name_str := linux.dirent_name(d) - - if pid, ok := strconv.parse_int(d_name_str); ok { - append(&dynamic_list, pid) - } - } - } - - list, err = slice.clone(dynamic_list[:], allocator) - return -} - -@(private="package") -_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - info.pid = pid - - // Use this to make cstrings without copying. - path_backing: [48]u8 - path_builder := strings.builder_from_bytes(path_backing[:]) - - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - proc_fd, errno := linux.open(strings.to_cstring(&path_builder) or_return, _OPENDIR_FLAGS) - if errno != .NONE { - err = _get_platform_error(errno) - return - } - defer linux.close(proc_fd) - - username_if: if .Username in selection { - s: linux.Stat - if errno = linux.fstat(proc_fd, &s); errno != .NONE { - err = _get_platform_error(errno) - break username_if - } - - passwd_bytes: []u8 - passwd_err: Error - passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator) - if passwd_err != nil { - err = passwd_err - break username_if - } - - passwd := string(passwd_bytes) - for len(passwd) > 0 { - n := strings.index_byte(passwd, ':') - if n < 0 { - break - } - username := passwd[:n] - passwd = passwd[n+1:] - - // skip password field - passwd = passwd[strings.index_byte(passwd, ':') + 1:] - - n = strings.index_byte(passwd, ':') - if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) { - info.username = strings.clone(username, allocator) or_return - info.fields += {.Username} - break - } else if !ok { - err = .Invalid_File - break username_if - } - - eol := strings.index_byte(passwd, '\n') - if eol < 0 { - break - } - passwd = passwd[eol + 1:] - } - } - - cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} { - strings.builder_reset(&path_builder) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - strings.write_string(&path_builder, "/cmdline") - - cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) - if cmdline_err != nil || len(cmdline_bytes) == 0 { - err = cmdline_err - break cmdline_if - } - cmdline := string(cmdline_bytes) - - terminator := strings.index_byte(cmdline, 0) - assert(terminator > 0) - - // command_line_exec := cmdline[:terminator] - - // Still need cwd if the execution on the command line is relative. - cwd: string - cwd_err: Error - if .Working_Dir in selection { - strings.builder_reset(&path_builder) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - strings.write_string(&path_builder, "/cwd") - - cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail - if cwd_err == nil && .Working_Dir in selection { - info.working_dir = strings.clone(cwd, allocator) or_return - info.fields += {.Working_Dir} - } else if cwd_err != nil { - err = cwd_err - break cmdline_if - } - } - - if selection & {.Command_Line, .Command_Args} != {} { - // skip to first arg - //cmdline = cmdline[terminator + 1:] - command_line_builder: strings.Builder - command_args_list: [dynamic]string - - if .Command_Line in selection { - command_line_builder = strings.builder_make(allocator) or_return - info.fields += {.Command_Line} - } - - for i := 0; len(cmdline) > 0; i += 1 { - if terminator = strings.index_byte(cmdline, 0); terminator < 0 { - break - } - - if .Command_Line in selection { - if i > 0 { - strings.write_byte(&command_line_builder, ' ') - } - strings.write_string(&command_line_builder, cmdline[:terminator]) - } - if .Command_Args in selection { - if i == 1 { - command_args_list = make([dynamic]string, allocator) or_return - info.fields += {.Command_Args} - } - if i > 0 { - arg := strings.clone(cmdline[:terminator], allocator) or_return - append(&command_args_list, arg) or_return - } - } - - cmdline = cmdline[terminator + 1:] - } - info.command_line = strings.to_string(command_line_builder) - info.command_args = command_args_list[:] - } - } - - stat_if: if selection & {.PPid, .Priority} != {} { - strings.builder_reset(&path_builder) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - strings.write_string(&path_builder, "/stat") - - proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) - if stat_err != nil { - err = stat_err - break stat_if - } - if len(proc_stat_bytes) <= 0 { - break stat_if - } - - // Skip to the first field after the executable name - stats: string - if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 { - stats = string(proc_stat_bytes[start + 2:]) - } else { - break stat_if - } - - // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat` - // because we skipped passed the executable name above. - Fields :: enum { - State, - PPid, - PGrp, - Session, - Tty_Nr, - TpGid, - Flags, - MinFlt, - CMinFlt, - MajFlt, - CMajFlt, - UTime, - STime, - CUTime, - CSTime, - Priority, - Nice, - //... etc, - } - stat_fields := strings.split(stats, " ", temp_allocator) or_return - - if len(stat_fields) <= int(Fields.Nice) { - break stat_if - } - - if .PPid in selection { - if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok { - info.ppid = ppid - info.fields += {.PPid} - } else { - err = .Invalid_File - break stat_if - } - } - - if .Priority in selection { - if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok { - info.priority = nice - info.fields += {.Priority} - } else { - err = .Invalid_File - break stat_if - } - } - } - - if .Executable_Path in selection { - /* - NOTE(Jeroen): - - The old version returned the wrong executable path for things like `bash` or `sh`, - for whom `/proc//cmdline` will just report "bash" or "sh", - resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there. - - Thanks to Yawning for suggesting `/proc/self/exe`. - */ - - strings.builder_reset(&path_builder) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - strings.write_string(&path_builder, "/exe") - - if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil { - info.executable_path = strings.clone(string(exe_bytes), allocator) or_return - info.fields += {.Executable_Path} - } else { - err = exe_err - } - } - - if .Environment in selection { - strings.builder_reset(&path_builder) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, pid) - strings.write_string(&path_builder, "/environ") - - if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil { - env := string(env_bytes) - - env_list := make([dynamic]string, allocator) or_return - for len(env) > 0 { - terminator := strings.index_byte(env, 0) - if terminator <= 0 { - break - } - e := strings.clone(env[:terminator], allocator) or_return - append(&env_list, e) or_return - env = env[terminator + 1:] - } - info.environment = env_list[:] - info.fields += {.Environment} - } else if err == nil { - err = env_err - } - } - - return -} - -@(private="package") -_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return _process_info_by_pid(process.pid, selection, allocator) -} - -@(private="package") -_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return _process_info_by_pid(get_pid(), selection, allocator) -} - -@(private="package") -_process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) { - process.pid = pid - process.handle = PIDFD_UNASSIGNED - - pidfd, errno := linux.pidfd_open(linux.Pid(pid), {}) - if errno == .ENOSYS { - return process, .Unsupported - } - if errno != .NONE { - return process, _get_platform_error(errno) - } - process.handle = uintptr(pidfd) - return -} - -@(private="package") -_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - if len(desc.command) == 0 { - return process, .Invalid_Command - } - - dir_fd := linux.AT_FDCWD - errno: linux.Errno - if desc.working_dir != "" { - dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return - if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { - return process, _get_platform_error(errno) - } - } - defer if desc.working_dir != "" { - linux.close(dir_fd) - } - - // search PATH if just a plain name is provided - exe_path: cstring - executable_name := desc.command[0] - if strings.index_byte(executable_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator) - path_dirs := split_path_list(path_env, temp_allocator) or_return - - exe_builder := strings.builder_make(temp_allocator) or_return - - found: bool - for dir in path_dirs { - strings.builder_reset(&exe_builder) - strings.write_string(&exe_builder, dir) - strings.write_byte(&exe_builder, '/') - strings.write_string(&exe_builder, executable_name) - - exe_path = strings.to_cstring(&exe_builder) or_return - stat := linux.Stat{} - if linux.stat(exe_path, &stat) == .NONE && .IFREG in stat.mode && .IXUSR in stat.mode { - found = true - break - } - } - if !found { - // check in cwd to match windows behavior - strings.builder_reset(&exe_builder) - strings.write_string(&exe_builder, "./") - strings.write_string(&exe_builder, executable_name) - - exe_path = strings.to_cstring(&exe_builder) or_return - if linux.access(exe_path, linux.X_OK) != .NONE { - return process, .Not_Exist - } - } - } else { - exe_path = clone_to_cstring(executable_name, temp_allocator) or_return - if linux.access(exe_path, linux.X_OK) != .NONE { - return process, .Not_Exist - } - } - - // args and environment need to be a list of cstrings - // that are terminated by a nil pointer. - cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return - for command, i in desc.command { - cargs[i] = clone_to_cstring(command, temp_allocator) or_return - } - - // Use current process' environment if description didn't provide it. - env: [^]cstring - if desc.env == nil { - // take this process's current environment - env = raw_data(export_cstring_environment(temp_allocator)) - } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return - for env, i in desc.env { - cenv[i] = clone_to_cstring(env, temp_allocator) or_return - } - env = &cenv[0] - } - - child_pipe_fds: [2]linux.Fd - if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE { - return process, _get_platform_error(errno) - } - defer linux.close(child_pipe_fds[READ]) - - // TODO: This is the traditional textbook implementation with fork. - // A more efficient implementation with vfork: - // - // 1. retrieve signal handlers - // 2. block all signals - // 3. allocate some stack space - // 4. vfork (waits for child exit or execve); In child: - // a. set child signal handlers - // b. set up any necessary pipes - // c. execve - // 5. restore signal handlers - // - pid: linux.Pid - if pid, errno = linux.fork(); errno != .NONE { - linux.close(child_pipe_fds[WRITE]) - return process, _get_platform_error(errno) - } - - STDIN :: linux.Fd(0) - STDOUT :: linux.Fd(1) - STDERR :: linux.Fd(2) - - READ :: 0 - WRITE :: 1 - - if pid == 0 { - // in child process now - write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! { - error_byte: [1]u8 = { u8(errno) } - linux.write(parent_fd, error_byte[:]) - linux.exit(126) - } - - stdin_fd: linux.Fd - stdout_fd: linux.Fd - stderr_fd: linux.Fd - - if desc.stdin != nil { - stdin_fd = linux.Fd(fd(desc.stdin)) - } else { - stdin_fd, errno = linux.open("/dev/null", {}) - if errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - } - - write_devnull: linux.Fd = -1 - - if desc.stdout != nil { - stdout_fd = linux.Fd(fd(desc.stdout)) - } else { - write_devnull, errno = linux.open("/dev/null", {.WRONLY}) - if errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - stdout_fd = write_devnull - } - - if desc.stderr != nil { - stderr_fd = linux.Fd(fd(desc.stderr)) - } else { - if write_devnull < 0 { - write_devnull, errno = linux.open("/dev/null", {.WRONLY}) - if errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - } - stderr_fd = write_devnull - } - - if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - if dir_fd != linux.AT_FDCWD { - if errno = linux.fchdir(dir_fd); errno != .NONE { - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - } - - errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) - assert(errno != nil) - write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) - } - - linux.close(child_pipe_fds[WRITE]) - - process.pid = int(pid) - - child_byte: [1]u8 - errno = .EINTR - for errno == .EINTR { - _, errno = linux.read(child_pipe_fds[READ], child_byte[:]) - } - - // If the read failed, something weird happened. Do not return the read - // error so the user knows to wait on it. - if errno == .NONE { - child_errno := linux.Errno(child_byte[0]) - if child_errno != .NONE { - // We can assume it trapped here. - _reap_terminated(process) - process.pid = 0 - return process, _get_platform_error(child_errno) - } - } - - process, _ = process_open(int(pid)) - return -} - -_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - stat_path_buf: [48]u8 - path_builder := strings.builder_from_bytes(stat_path_buf[:]) - strings.write_string(&path_builder, "/proc/") - strings.write_int(&path_builder, int(state.pid)) - strings.write_string(&path_builder, "/stat") - - stat_buf: []u8 - stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) - if err != nil { - return - } - - // ')' will be the end of the executable name (item 2) - idx := strings.last_index_byte(string(stat_buf), ')') - stats := string(stat_buf[idx + 2:]) - - // utime and stime are the 14 and 15th items, respectively, and we are - // currently on item 3. Skip 11 items here. - for _ in 0..<11 { - stats = stats[strings.index_byte(stats, ' ') + 1:] - } - - idx = strings.index_byte(stats, ' ') - utime_str := stats[:idx] - - stats = stats[idx + 1:] - stime_str := stats[:strings.index_byte(stats, ' ')] - - utime, stime: int - ok: bool - if utime, ok = strconv.parse_int(utime_str, 10); !ok { - return .Invalid_File - } - if stime, ok = strconv.parse_int(stime_str, 10); !ok { - return .Invalid_File - } - - // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms - state.user_time = time.Duration(utime) * 10 * time.Millisecond - state.system_time = time.Duration(stime) * 10 * time.Millisecond - - return -} - -_reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) { - state.pid = process.pid - _process_state_update_times(&state) - - info: linux.Sig_Info - errno := linux.Errno.EINTR - for errno == .EINTR { - errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil) - } - err = _get_platform_error(errno) - - switch linux.Sig_Child_Code(info.code) { - case .NONE, .CONTINUED, .STOPPED: - unreachable() - case .EXITED: - state.exited = true - state.exit_code = int(info.status) - state.success = state.exit_code == 0 - case .KILLED, .DUMPED, .TRAPPED: - state.exited = true - state.exit_code = int(info.status) - state.success = false - } - return -} - -_timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - timeout := timeout - - process_state.pid = process.pid - pidfd := linux.Fd(process.handle) - pollfd: [1]linux.Poll_Fd = { - { - fd = pidfd, - events = {.IN}, - }, - } - - start_tick := time.tick_now() - - mask: bit_set[0..<64; u64] - mask += { int(linux.Signal.SIGCHLD) - 1 } - sigchld_set := transmute(linux.Sig_Set)(mask) - - info: linux.Sig_Info - for { - if timeout <= 0 { - _process_state_update_times(&process_state) - err = .Timeout - return - } - - ts: linux.Time_Spec = { - time_sec = uint(timeout / time.Second), - time_nsec = uint(timeout % time.Second), - } - - n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set) - if errno != .NONE { - if errno == .EINTR { - timeout -= time.tick_since(start_tick) - start_tick = time.tick_now() - continue - } - return process_state, _get_platform_error(errno) - } - - if n == 0 { // timeout with no events - _process_state_update_times(&process_state) - err = .Timeout - return - } - - if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE { - return process_state, _get_platform_error(errno) - } - - if info.signo == .SIGCHLD { - break - } - - timeout -= time.tick_since(start_tick) - start_tick = time.tick_now() - } - - // _reap_terminated for pidfd - { - _process_state_update_times(&process_state) - - errno := linux.Errno.EINTR - for errno == .EINTR { - errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil) - } - err = _get_platform_error(errno) - - switch linux.Sig_Child_Code(info.code) { - case .NONE, .CONTINUED, .STOPPED: - unreachable() - case .EXITED: - process_state.exited = true - process_state.exit_code = int(info.status) - process_state.success = process_state.exit_code == 0 - case .KILLED, .DUMPED, .TRAPPED: - process_state.exited = true - process_state.exit_code = int(info.status) - process_state.success = false - } - } - return -} - -_timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - timeout := timeout - process_state.pid = process.pid - - mask: bit_set[0..<64; u64] - mask += { int(linux.Signal.SIGCHLD) - 1 } - sigchld_set := transmute(linux.Sig_Set)(mask) - - start_tick := time.tick_now() - - org_sigset: linux.Sig_Set - errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset) - if errno != .NONE { - return process_state, _get_platform_error(errno) - } - defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) - - // In case there was a signal handler on SIGCHLD, avoid race - // condition by checking wait first. - info: linux.Sig_Info - errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil) - - for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) { - if timeout <= 0 { - _process_state_update_times(&process_state) - err = .Timeout - return - } - - ts: linux.Time_Spec = { - time_sec = uint(timeout / time.Second), - time_nsec = uint(timeout % time.Second), - } - - _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts) - #partial switch errno { - case .EAGAIN: // timeout - _process_state_update_times(&process_state) - err = .Timeout - return - case .EINTR: - timeout -= time.tick_since(start_tick) - start_tick = time.tick_now() - case .EINVAL: - return process_state, _get_platform_error(errno) - } - } - - return _reap_terminated(process) -} - -@(private="package") -_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { - if timeout > 0 { - if process.handle == PIDFD_UNASSIGNED { - return _timed_wait_on_pid(process, timeout) - } else { - return _timed_wait_on_handle(process, timeout) - } - } - - process_state: Process_State = { - pid = process.pid, - } - - errno: linux.Errno - options: linux.Wait_Options = {.WEXITED} - if timeout == 0 { - options += {.WNOHANG} - } - - info: linux.Sig_Info - - errno = .EINTR - for errno == .EINTR { - errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil) - } - if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) { - _process_state_update_times(&process_state) - return process_state, .Timeout - } - if errno != .NONE { - return process_state, _get_platform_error(errno) - } - - return _reap_terminated(process) -} - -@(private="package") -_process_close :: proc(process: Process) -> Error { - if process.handle == 0 || process.handle == PIDFD_UNASSIGNED { - return nil - } - pidfd := linux.Fd(process.handle) - return _get_platform_error(linux.close(pidfd)) -} - -@(private="package") -_process_kill :: proc(process: Process) -> Error { - return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL)) -} - diff --git a/core/os/os2/process_netbsd.odin b/core/os/os2/process_netbsd.odin deleted file mode 100644 index b46a58e58..000000000 --- a/core/os/os2/process_netbsd.odin +++ /dev/null @@ -1,31 +0,0 @@ -#+private -#+build netbsd -package os2 - -import "core:c" -foreign import libc "system:c" - -@(private) -foreign libc { - _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 deleted file mode 100644 index 9c6605952..000000000 --- a/core/os/os2/process_openbsd.odin +++ /dev/null @@ -1,25 +0,0 @@ -#+private -#+build openbsd -package os2 - -import "core:c" - -foreign import libc "system:c" - -@(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.odin b/core/os/os2/process_posix.odin deleted file mode 100644 index a48e44900..000000000 --- a/core/os/os2/process_posix.odin +++ /dev/null @@ -1,344 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -import "core:time" -import "core:strings" - -import kq "core:sys/kqueue" -import "core:sys/posix" - -_get_uid :: proc() -> int { - return int(posix.getuid()) -} - -_get_euid :: proc() -> int { - return int(posix.geteuid()) -} - -_get_gid :: proc() -> int { - return int(posix.getgid()) -} - -_get_egid :: proc() -> int { - return int(posix.getegid()) -} - -_get_pid :: proc() -> int { - return int(posix.getpid()) -} - -_get_ppid :: proc() -> int { - return int(posix.getppid()) -} - -_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return _process_info_by_pid(process.pid, selection, allocator) -} - -_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - return _process_info_by_pid(_get_pid(), selection, allocator) -} - -_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - if len(desc.command) == 0 { - err = .Invalid_Path - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - - // search PATH if just a plain name is provided. - exe_builder := strings.builder_make(temp_allocator) - exe_name := desc.command[0] - if strings.index_byte(exe_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator) - path_dirs := split_path_list(path_env, temp_allocator) or_return - - found: bool - for dir in path_dirs { - strings.builder_reset(&exe_builder) - strings.write_string(&exe_builder, dir) - strings.write_byte(&exe_builder, '/') - strings.write_string(&exe_builder, exe_name) - - if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { - continue - } else { - posix.close(exe_fd) - found = true - break - } - } - if !found { - // check in cwd to match windows behavior - strings.builder_reset(&exe_builder) - strings.write_string(&exe_builder, desc.working_dir) - if len(desc.working_dir) > 0 && desc.working_dir[len(desc.working_dir)-1] != '/' { - strings.write_byte(&exe_builder, '/') - } - strings.write_string(&exe_builder, "./") - strings.write_string(&exe_builder, exe_name) - - // "hello/./world" is fine right? - - if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { - err = .Not_Exist - return - } else { - posix.close(exe_fd) - } - } - } else { - strings.builder_reset(&exe_builder) - strings.write_string(&exe_builder, exe_name) - - if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { - err = .Not_Exist - return - } else { - posix.close(exe_fd) - } - } - - cwd: cstring; if desc.working_dir != "" { - cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return - } - - cmd := make([]cstring, len(desc.command) + 1, temp_allocator) - for part, i in desc.command { - cmd[i] = clone_to_cstring(part, temp_allocator) or_return - } - - env: [^]cstring - if desc.env == nil { - // take this process's current environment - env = posix.environ - } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator) - for env, i in desc.env { - cenv[i] = clone_to_cstring(env, temp_allocator) or_return - } - env = raw_data(cenv) - } - - READ :: 0 - WRITE :: 1 - - pipe: [2]posix.FD - if posix.pipe(&pipe) != .OK { - err = _get_platform_error() - return - } - defer posix.close(pipe[READ]) - - if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { - posix.close(pipe[WRITE]) - err = _get_platform_error() - return - } - if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { - posix.close(pipe[WRITE]) - err = _get_platform_error() - return - } - - switch pid := posix.fork(); pid { - case -1: - posix.close(pipe[WRITE]) - err = _get_platform_error() - return - - case 0: - abort :: proc(parent_fd: posix.FD) -> ! { - #assert(len(posix.Errno) < max(u8)) - errno := u8(posix.errno()) - posix.write(parent_fd, &errno, 1) - posix.exit(126) - } - - null := posix.open("/dev/null", {.RDWR}) - if null == -1 { abort(pipe[WRITE]) } - - stderr := (^File_Impl)(desc.stderr.impl).fd if desc.stderr != nil else null - stdout := (^File_Impl)(desc.stdout.impl).fd if desc.stdout != nil else null - stdin := (^File_Impl)(desc.stdin.impl).fd if desc.stdin != nil else null - - if posix.dup2(stderr, posix.STDERR_FILENO) == -1 { abort(pipe[WRITE]) } - if posix.dup2(stdout, posix.STDOUT_FILENO) == -1 { abort(pipe[WRITE]) } - if posix.dup2(stdin, posix.STDIN_FILENO ) == -1 { abort(pipe[WRITE]) } - - if cwd != nil { - if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } - } - - res := posix.execve(strings.to_cstring(&exe_builder) or_return, raw_data(cmd), env) - assert(res == -1) - abort(pipe[WRITE]) - - case: - posix.close(pipe[WRITE]) - - errno: posix.Errno - for { - errno_byte: u8 - switch posix.read(pipe[READ], &errno_byte, 1) { - case 1: - errno = posix.Errno(errno_byte) - case -1: - errno = posix.errno() - if errno == .EINTR { - continue - } else { - // If the read failed, something weird happened. Do not return the read - // error so the user knows to wait on it. - errno = nil - } - } - break - } - - if errno != nil { - // We can assume it trapped here. - - for { - info: posix.siginfo_t - wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) - if wpid == -1 && posix.errno() == .EINTR { - continue - } - break - } - - err = errno - return - } - - process, _ = _process_open(int(pid), {}) - return - } -} - -_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - process_state.pid = process.pid - - _process_handle_still_valid(process) or_return - - // timeout > 0 = use kqueue to wait (with a timeout) on process exit - // timeout == 0 = use waitid with WNOHANG so it returns immediately - // timeout > 0 = use waitid without WNOHANG so it waits indefinitely - // - // at the end use waitid to actually reap the process and get it's status - - if timeout > 0 { - timeout := timeout - - queue := kq.kqueue() or_return - defer posix.close(queue) - - changelist, eventlist: [1]kq.KEvent - - changelist[0] = { - ident = uintptr(process.pid), - filter = .Proc, - flags = { .Add }, - fflags = { - fproc = { .Exit }, - }, - } - - for { - start := time.tick_now() - n, kerr := kq.kevent(queue, changelist[:], eventlist[:], &{ - tv_sec = posix.time_t(timeout / time.Second), - tv_nsec = i64(timeout % time.Second), - }) - if kerr == .EINTR { - timeout -= time.tick_since(start) - continue - } else if kerr != nil { - err = kerr - return - } else if n == 0 { - err = .Timeout - _process_state_update_times(process, &process_state) - return - } else { - _process_state_update_times(process, &process_state) - break - } - } - } else { - flags := posix.Wait_Flags{.EXITED, .NOWAIT} - if timeout == 0 { - flags += {.NOHANG} - } - - info: posix.siginfo_t - for { - wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, flags) - if wpid == -1 { - if errno := posix.errno(); errno == .EINTR { - continue - } else { - err = _get_platform_error() - return - } - } - break - } - - _process_state_update_times(process, &process_state) - - if info.si_signo == nil { - assert(timeout == 0) - err = .Timeout - return - } - } - - info: posix.siginfo_t - for { - wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) - if wpid == -1 { - if errno := posix.errno(); errno == .EINTR { - continue - } else { - err = _get_platform_error() - return - } - } - break - } - - switch info.si_code.chld { - case: unreachable() - case .CONTINUED, .STOPPED: unreachable() - case .EXITED: - process_state.exited = true - process_state.exit_code = int(info.si_status) - process_state.success = process_state.exit_code == 0 - case .KILLED, .DUMPED, .TRAPPED: - process_state.exited = true - process_state.exit_code = int(info.si_status) - process_state.success = false - } - - return -} - -_process_close :: proc(process: Process) -> Error { - return nil -} - -_process_kill :: proc(process: Process) -> (err: Error) { - _process_handle_still_valid(process) or_return - - if posix.kill(posix.pid_t(process.pid), .SIGKILL) != .OK { - err = _get_platform_error() - } - - return -} diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin deleted file mode 100644 index 934d23711..000000000 --- a/core/os/os2/process_posix_darwin.odin +++ /dev/null @@ -1,332 +0,0 @@ -#+private -package os2 - -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 pthread "system:System" - -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) -} - -_get_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) { - get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) { - // Short info is enough and requires less permissions if the priority isn't requested. - if .Priority in selection { - info: darwin.proc_taskallinfo - ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info)) - if ret > 0 { - assert(ret == size_of(info)) - ppid = info.pbsd.pbi_ppid - prio = info.ptinfo.pti_priority - uid = info.pbsd.pbi_uid - ok = true - return - } - } - - // Try short info, requires less permissions, but doesn't give a `nice`. - psinfo: darwin.proc_bsdshortinfo - ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo)) - if ret > 0 { - assert(ret == size_of(psinfo)) - ppid = psinfo.pbsi_ppid - uid = psinfo.pbsi_uid - ok = true - } - - return - } - - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - info.pid = pid - - // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), - // other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information. - - pidinfo: { - if selection & {.PPid, .Priority, .Username } != {} { - ppid, mprio, uid, ok := get_pidinfo(pid, selection) - if !ok { - if err == nil { - err = _get_platform_error() - } - break pidinfo - } - - if .PPid in selection { - info.ppid = int(ppid) - info.fields += {.PPid} - } - - if prio, has_prio := mprio.?; has_prio && .Priority in selection { - info.priority = int(prio) - info.fields += {.Priority} - } - - if .Username in selection { - pw := posix.getpwuid(uid) - if pw == nil { - if err == nil { - err = _get_platform_error() - } - break pidinfo - } - - info.username = clone_string(string(pw.pw_name), allocator) or_return - info.fields += {.Username} - } - } - } - - if .Working_Dir in selection { - pinfo: darwin.proc_vnodepathinfo - ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo)) - if ret > 0 { - assert(ret == size_of(pinfo)) - info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return - info.fields += {.Working_Dir} - } else if err == nil { - err = _get_platform_error() - } - } - - if .Executable_Path in selection { - buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- - ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer)) - if ret > 0 { - info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return - info.fields += {.Executable_Path} - } else if err == nil { - err = _get_platform_error() - } - } - - args: if selection & { .Command_Line, .Command_Args, .Environment } != {} { - mib := []i32{ - unix.CTL_KERN, - unix.KERN_PROCARGS2, - i32(pid), - } - length: uint - if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK { - if err == nil { - err = _get_platform_error() - } - break args - } - - buf := runtime.make_aligned([]byte, length, 4, temp_allocator) - if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { - if err == nil { - err = _get_platform_error() - - // Looks like EINVAL is returned here if you don't have permission. - if err == Platform_Error(posix.Errno.EINVAL) { - err = .Permission_Denied - } - } - break args - } - - buf = buf[:length] - - if len(buf) < 4 { - break args - } - - // Layout isn't really documented anywhere, I deduced it to be: - // i32 - argc - // cstring - command name (skipped) - // [^]byte - couple of 0 bytes (skipped) - // [^]cstring - argv (up to argc entries) - // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too) - - argc := (^i32)(raw_data(buf))^ - buf = buf[size_of(i32):] - - { - command_line: [dynamic]byte - command_line.allocator = allocator - - argv: [dynamic]string - argv.allocator = allocator - - defer if err != nil { - for arg in argv { delete(arg, allocator) } - delete(argv) - delete(command_line) - } - - _, _ = bytes.split_iterator(&buf, {0}) - buf = bytes.trim_left(buf, {0}) - - first_arg := true - for arg in bytes.split_iterator(&buf, {0}) { - if .Command_Line in selection { - if !first_arg { - append(&command_line, ' ') or_return - } - append(&command_line, ..arg) or_return - } - - if .Command_Args in selection { - sarg := clone_string(string(arg), allocator) or_return - append(&argv, sarg) or_return - } - - first_arg = false - argc -= 1 - if argc == 0 { - break - } - } - - if .Command_Line in selection { - info.command_line = string(command_line[:]) - info.fields += {.Command_Line} - } - if .Command_Args in selection { - info.command_args = argv[:] - info.fields += {.Command_Args} - } - } - - if .Environment in selection { - environment: [dynamic]string - environment.allocator = allocator - - defer if err != nil { - for entry in environment { delete(entry, allocator) } - delete(environment) - } - - for entry in bytes.split_iterator(&buf, {0}) { - if bytes.index_byte(entry, '=') > -1 { - sentry := clone_string(string(entry), allocator) or_return - append(&environment, sentry) or_return - } - } - - info.environment = environment[:] - info.fields += {.Environment} - } - } - - // Fields were requested that we didn't add. - if err == nil && selection - info.fields != {} { - err = .Unsupported - } - - return -} - -_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { - ret := darwin.proc_listallpids(nil, 0) - if ret < 0 { - err = _get_platform_error() - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - buffer := make([]i32, ret, temp_allocator) - ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) - if ret < 0 { - err = _get_platform_error() - return - } - - list = make([]int, ret, allocator) or_return - #no_bounds_check for &entry, i in list { - entry = int(buffer[i]) - } - - return -} - -_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { - rusage: darwin.rusage_info_v0 - if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 { - err = _get_platform_error() - return - } - - // Using the start time as the handle, there is no pidfd or anything on Darwin. - // There is a uuid, but once a process becomes a zombie it changes... - process.handle = uintptr(rusage.ri_proc_start_abstime) - process.pid = int(pid) - return -} - -_process_handle_still_valid :: proc(p: Process) -> Error { - rusage: darwin.rusage_info_v0 - if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { - return _get_platform_error() - } - - handle := uintptr(rusage.ri_proc_start_abstime) - if p.handle != handle { - return posix.Errno.ESRCH - } - - return nil -} - -_process_state_update_times :: proc(p: Process, state: ^Process_State) { - rusage: darwin.rusage_info_v0 - if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { - return - } - - // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output. - HZ :: 20000000 - - state.user_time = ( - (time.Duration(rusage.ri_user_time) / HZ * time.Second) + - time.Duration(rusage.ri_user_time % HZ)) - state.system_time = ( - (time.Duration(rusage.ri_system_time) / HZ * time.Second) + - time.Duration(rusage.ri_system_time % HZ)) - - return -} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin deleted file mode 100644 index 65da3e9e2..000000000 --- a/core/os/os2/process_posix_other.odin +++ /dev/null @@ -1,29 +0,0 @@ -#+private -#+build netbsd, openbsd, freebsd -package os2 - -import "base:runtime" - -_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/process_wasi.odin b/core/os/os2/process_wasi.odin deleted file mode 100644 index efb2c0228..000000000 --- a/core/os/os2/process_wasi.odin +++ /dev/null @@ -1,91 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:time" -// import "core:sys/wasm/wasi" - -_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 -} - -_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 -} - -_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/process_windows.odin b/core/os/os2/process_windows.odin deleted file mode 100644 index b2c87c4f4..000000000 --- a/core/os/os2/process_windows.odin +++ /dev/null @@ -1,799 +0,0 @@ -#+private file -package os2 - -import "base:intrinsics" -import "base:runtime" - -import "core:strings" -import win32 "core:sys/windows" -import "core:time" - -@(private="package") -_get_uid :: proc() -> int { - return -1 -} - -@(private="package") -_get_euid :: proc() -> int { - return -1 -} - -@(private="package") -_get_gid :: proc() -> int { - return -1 -} - -@(private="package") -_get_egid :: proc() -> int { - return -1 -} - -@(private="package") -_get_pid :: proc() -> int { - return int(win32.GetCurrentProcessId()) -} - -@(private="package") -_get_ppid :: proc() -> int { - our_pid := win32.GetCurrentProcessId() - snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) - if snap == win32.INVALID_HANDLE_VALUE { - return -1 - } - defer win32.CloseHandle(snap) - entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } - for status := win32.Process32FirstW(snap, &entry); status; /**/ { - if entry.th32ProcessID == our_pid { - return int(entry.th32ParentProcessID) - } - status = win32.Process32NextW(snap, &entry) - } - 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) - if snap == win32.INVALID_HANDLE_VALUE { - err = _get_platform_error() - return - } - - list_d := make([dynamic]int, allocator) or_return - - entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} - status := win32.Process32FirstW(snap, &entry) - for status { - append(&list_d, int(entry.th32ProcessID)) - status = win32.Process32NextW(snap, &entry) - } - list = list_d[:] - return -} - -@(require_results) -read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) { - if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) { - err = _get_platform_error() - } - return -} -@(require_results) -read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) { - if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) { - err = _get_platform_error() - } - return -} - -@(private="package") -_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - info.pid = pid - // Note(flysand): Open the process handle right away to prevent some race - // conditions. Once the handle is open, the process will be kept alive by - // the OS. - ph := win32.INVALID_HANDLE_VALUE - if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { - ph = win32.OpenProcess( - win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, - false, - u32(pid), - ) - if ph == win32.INVALID_HANDLE_VALUE { - err = _get_platform_error() - return - } - } - defer if ph != win32.INVALID_HANDLE_VALUE { - win32.CloseHandle(ph) - } - snapshot_process: if selection >= {.PPid, .Priority} { - entry, entry_err := _process_entry_by_pid(info.pid) - if entry_err != nil { - err = entry_err - if entry_err == General_Error.Not_Exist { - return - } else { - break snapshot_process - } - } - if .PPid in selection { - info.fields += {.PPid} - info.ppid = int(entry.th32ParentProcessID) - } - if .Priority in selection { - info.fields += {.Priority} - info.priority = int(entry.pcPriClassBase) - } - } - snapshot_modules: if .Executable_Path in selection { - exe_path: string - exe_path, err = _process_exe_by_pid(pid, allocator) - if _, ok := err.(runtime.Allocator_Error); ok { - return - } else if err != nil { - break snapshot_modules - } - info.executable_path = exe_path - info.fields += {.Executable_Path} - } - read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { - process_info_size: u32 - process_info: win32.PROCESS_BASIC_INFORMATION - status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) - if status != 0 { - // TODO(flysand): There's probably a mismatch between NTSTATUS and - // windows userland error codes, I haven't checked. - err = Platform_Error(status) - break read_peb - } - assert(process_info.PebBaseAddress != nil) - process_peb: win32.PEB - _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) - if err != nil { - break read_peb - } - process_params: win32.RTL_USER_PROCESS_PARAMETERS - _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) - if err != nil { - break read_peb - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - if selection >= {.Command_Line, .Command_Args} { - temp_allocator_scope(temp_allocator) - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) - if err != nil { - break read_peb - } - if .Command_Line in selection { - info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return - info.fields += {.Command_Line} - } - if .Command_Args in selection { - info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return - info.fields += {.Command_Args} - } - } - if .Environment in selection { - temp_allocator_scope(temp_allocator) - env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) - if err != nil { - break read_peb - } - info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return - info.fields += {.Environment} - } - if .Working_Dir in selection { - temp_allocator_scope(temp_allocator) - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) - if err != nil { - break read_peb - } - info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return - info.fields += {.Working_Dir} - } - } - read_username: if .Username in selection { - username: string - username, err = _get_process_user(ph, allocator) - if _, ok := err.(runtime.Allocator_Error); ok { - return - } else if err != nil { - break read_username - } - info.username = username - info.fields += {.Username} - } - err = nil - return -} - -@(private="package") -_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - pid := process.pid - info.pid = pid - // Data obtained from process snapshots - snapshot_process: if selection >= {.PPid, .Priority} { - entry, entry_err := _process_entry_by_pid(info.pid) - if entry_err != nil { - err = entry_err - if entry_err == General_Error.Not_Exist { - return - } else { - break snapshot_process - } - } - if .PPid in selection { - info.fields += {.PPid} - info.ppid = int(entry.th32ParentProcessID) - } - if .Priority in selection { - info.fields += {.Priority} - info.priority = int(entry.pcPriClassBase) - } - } - snapshot_module: if .Executable_Path in selection { - exe_path: string - exe_path, err = _process_exe_by_pid(pid, allocator) - if _, ok := err.(runtime.Allocator_Error); ok { - return - } else if err != nil { - break snapshot_module - } - info.executable_path = exe_path - info.fields += {.Executable_Path} - } - ph := win32.HANDLE(process.handle) - read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { - process_info_size: u32 - process_info: win32.PROCESS_BASIC_INFORMATION - status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) - if status != 0 { - // TODO(flysand): There's probably a mismatch between NTSTATUS and - // windows userland error codes, I haven't checked. - err = Platform_Error(status) - return - } - assert(process_info.PebBaseAddress != nil) - process_peb: win32.PEB - _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) - if err != nil { - break read_peb - } - process_params: win32.RTL_USER_PROCESS_PARAMETERS - _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) - if err != nil { - break read_peb - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - if selection >= {.Command_Line, .Command_Args} { - temp_allocator_scope(temp_allocator) - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) - if err != nil { - break read_peb - } - if .Command_Line in selection { - info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return - info.fields += {.Command_Line} - } - if .Command_Args in selection { - info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return - info.fields += {.Command_Args} - } - } - if .Environment in selection { - temp_allocator_scope(temp_allocator) - env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) - if err != nil { - break read_peb - } - info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return - info.fields += {.Environment} - } - if .Working_Dir in selection { - temp_allocator_scope(temp_allocator) - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return - _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) - if err != nil { - break read_peb - } - info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return - info.fields += {.Working_Dir} - } - } - read_username: if .Username in selection { - username: string - username, err = _get_process_user(ph, allocator) - if _, ok := err.(runtime.Allocator_Error); ok { - return - } else if err != nil { - break read_username - } - info.username = username - info.fields += {.Username} - } - err = nil - return -} - -@(private="package") -_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - info.pid = get_pid() - snapshot_process: if selection >= {.PPid, .Priority} { - entry, entry_err := _process_entry_by_pid(info.pid) - if entry_err != nil { - err = entry_err - if entry_err == General_Error.Not_Exist { - return - } else { - break snapshot_process - } - } - if .PPid in selection { - info.fields += {.PPid} - info.ppid = int(entry.th32ProcessID) - } - if .Priority in selection { - info.fields += {.Priority} - info.priority = int(entry.pcPriClassBase) - } - } - module_filename: if .Executable_Path in selection { - exe_filename_w: [256]u16 - path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) - assert(path_len > 0) - info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return - info.fields += {.Executable_Path} - } - command_line: if selection >= {.Command_Line, .Command_Args} { - command_line_w := win32.GetCommandLineW() - assert(command_line_w != nil) - if .Command_Line in selection { - info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return - info.fields += {.Command_Line} - } - if .Command_Args in selection { - info.command_args = _parse_command_line(command_line_w, allocator) or_return - info.fields += {.Command_Args} - } - } - read_environment: if .Environment in selection { - env_block := win32.GetEnvironmentStringsW() - assert(env_block != nil) - info.environment = _parse_environment_block(env_block, allocator) or_return - info.fields += {.Environment} - } - read_username: if .Username in selection { - process_handle := win32.GetCurrentProcess() - username: string - username, err = _get_process_user(process_handle, allocator) - if _, ok := err.(runtime.Allocator_Error); ok { - return - } else if err != nil { - break read_username - } - info.username = username - info.fields += {.Username} - } - if .Working_Dir in selection { - // TODO(flysand): Implement this by reading PEB - err = .Mode_Not_Implemented - return - } - err = nil - return -} - -@(private="package") -_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { - // Note(flysand): The handle will be used for querying information so we - // take the necessary permissions right away. - dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE - if .Mem_Read in flags { - dwDesiredAccess |= win32.PROCESS_VM_READ - } - if .Mem_Write in flags { - dwDesiredAccess |= win32.PROCESS_VM_WRITE - } - handle := win32.OpenProcess( - dwDesiredAccess, - false, - u32(pid), - ) - if handle == win32.INVALID_HANDLE_VALUE { - err = _get_platform_error() - } else { - process = {pid = pid, handle = uintptr(handle)} - } - return -} - -@(private="package") -_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - command_line := _build_command_line(desc.command, temp_allocator) - command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return - environment := desc.env - if desc.env == nil { - environment = environ(temp_allocator) or_return - } - environment_block := _build_environment_block(environment, temp_allocator) - environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return - - stderr_handle: win32.HANDLE - stdout_handle: win32.HANDLE - stdin_handle: win32.HANDLE - - null_handle: win32.HANDLE - if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil { - null_handle = win32.CreateFileW( - win32.L("NUL"), - win32.GENERIC_READ|win32.GENERIC_WRITE, - win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE, - &win32.SECURITY_ATTRIBUTES{ - nLength = size_of(win32.SECURITY_ATTRIBUTES), - bInheritHandle = true, - }, - win32.OPEN_EXISTING, - win32.FILE_ATTRIBUTE_NORMAL, - nil, - ) - // Opening NUL should always succeed. - assert(null_handle != nil) - } - // NOTE(laytan): I believe it is fine to close this handle right after CreateProcess, - // and we don't have to hold onto this until the process exits. - defer if null_handle != nil { - win32.CloseHandle(null_handle) - } - - if desc.stdout == nil { - stdout_handle = null_handle - } else { - stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) - } - - if desc.stderr == nil { - stderr_handle = null_handle - } else { - stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) - } - - if desc.stdin == nil { - stdin_handle = null_handle - } else { - stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) - } - - working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil - process_info: win32.PROCESS_INFORMATION - ok := win32.CreateProcessW( - nil, - command_line_w, - nil, - nil, - true, - win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, - raw_data(environment_block_w), - working_dir_w, - &win32.STARTUPINFOW{ - cb = size_of(win32.STARTUPINFOW), - hStdError = stderr_handle, - hStdOutput = stdout_handle, - hStdInput = stdin_handle, - dwFlags = win32.STARTF_USESTDHANDLES, - }, - &process_info, - ) - if !ok { - err = _get_platform_error() - return - } - process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} - return -} - -@(private="package") -_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { - handle := win32.HANDLE(process.handle) - timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE - - switch win32.WaitForSingleObject(handle, timeout_ms) { - case win32.WAIT_OBJECT_0: - exit_code: u32 - if !win32.GetExitCodeProcess(handle, &exit_code) { - err =_get_platform_error() - return - } - time_created: win32.FILETIME - time_exited: win32.FILETIME - time_kernel: win32.FILETIME - time_user: win32.FILETIME - if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { - err = _get_platform_error() - return - } - process_state = { - exit_code = int(exit_code), - exited = true, - pid = process.pid, - success = true, - system_time = _filetime_to_duration(time_kernel), - user_time = _filetime_to_duration(time_user), - } - return - case win32.WAIT_TIMEOUT: - err = General_Error.Timeout - return - case: - err = _get_platform_error() - return - } -} - -@(private="package") -_process_close :: proc(process: Process) -> Error { - if !win32.CloseHandle(win32.HANDLE(process.handle)) { - return _get_platform_error() - } - return nil -} - -@(private="package") -_process_kill :: proc(process: Process) -> Error { - // Note(flysand): This is different than what the task manager's "kill process" - // functionality does, as we don't try to send WM_CLOSE message first. This - // is quite a rough way to kill the process, which should be consistent with - // linux. The error code 9 is to mimic SIGKILL event. - if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { - return _get_platform_error() - } - return nil -} - -_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { - ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) - return time.Duration(ticks * 100) -} - -_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { - snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) - if snap == win32.INVALID_HANDLE_VALUE { - err = _get_platform_error() - return - } - defer win32.CloseHandle(snap) - - entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} - status := win32.Process32FirstW(snap, &entry) - for status { - if u32(pid) == entry.th32ProcessID { - return - } - status = win32.Process32NextW(snap, &entry) - } - err = General_Error.Not_Exist - return -} - -// Note(flysand): Not sure which way it's better to get the executable path: -// via toolhelp snapshots or by reading other process' PEB memory. I have -// a slight suspicion that if both exe path and command line are desired, -// it's faster to just read both from PEB, but maybe the toolhelp snapshots -// are just better...? -@(private="package") -_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { - snap := win32.CreateToolhelp32Snapshot( - win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, - u32(pid), - ) - if snap == win32.INVALID_HANDLE_VALUE { - err =_get_platform_error() - return - } - defer win32.CloseHandle(snap) - - entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } - status := win32.Module32FirstW(snap, &entry) - if !status { - err =_get_platform_error() - return - } - return win32_wstring_to_utf8(cstring16(raw_data(entry.szExePath[:])), allocator) -} - -_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - token_handle: win32.HANDLE - if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { - err = _get_platform_error() - return - } - token_user_size: u32 - if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { - // Note(flysand): Make sure the buffer too small error comes out, and not any other error - err = _get_platform_error() - if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { - return - } - err = nil - } - token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return)) - if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { - err = _get_platform_error() - return - } - - sid_type: win32.SID_NAME_USE - username_w: [256]u16 - domain_w: [256]u16 - username_chrs := u32(256) - domain_chrs := u32(256) - - if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { - err = _get_platform_error() - return - } - username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return - domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return - return strings.concatenate({domain, "\\", username}, allocator) -} - -_parse_command_line :: proc(cmd_line_w: cstring16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { - argc: i32 - argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) - if argv_w == nil { - return nil, _get_platform_error() - } - argv = make([]string, argc, allocator) or_return - defer if err != nil { - for arg in argv { - delete(arg, allocator) - } - delete(argv, allocator) - } - for arg_w, i in argv_w[:argc] { - argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return - } - return -} - -_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { - _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { - for _ in 0 ..< n { - strings.write_byte(builder, b) - } - } - builder := strings.builder_make(allocator) - for arg, i in command { - if i != 0 { - strings.write_byte(&builder, ' ') - } - j := 0 - if strings.contains_any(arg, "()[]{}^=;!'+,`~\" ") { - strings.write_byte(&builder, '"') - for j < len(arg) { - backslashes := 0 - for j < len(arg) && arg[j] == '\\' { - backslashes += 1 - j += 1 - } - if j == len(arg) { - _write_byte_n_times(&builder, '\\', 2*backslashes) - break - } else if arg[j] == '"' { - _write_byte_n_times(&builder, '\\', 2*backslashes+1) - strings.write_byte(&builder, arg[j]) - } else { - _write_byte_n_times(&builder, '\\', backslashes) - strings.write_byte(&builder, arg[j]) - } - j += 1 - } - strings.write_byte(&builder, '"') - } else { - strings.write_string(&builder, arg) - } - } - return strings.to_string(builder) -} - -_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) { - zt_count := 0 - for idx := 0; true; { - if block[idx] == 0x0000 { - zt_count += 1 - if block[idx+1] == 0x0000 { - zt_count += 1 - break - } - } - idx += 1 - } - - // Note(flysand): Each string in the environment block is terminated - // by a NUL character. In addition, the environment block itself is - // terminated by a NUL character. So the number of strings in the - // environment block is the number of NUL character minus the - // block terminator. - env_count := zt_count - 1 - envs = make([]string, env_count, allocator) or_return - defer if err != nil { - for env in envs { - delete(env, allocator) - } - delete(envs, allocator) - } - - env_idx := 0 - last_idx := 0 - idx := 0 - for block[idx] != 0x0000 { - for block[idx] != 0x0000 { - idx += 1 - } - env_w := block[last_idx:idx] - envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return - env_idx += 1 - idx += 1 - last_idx = idx - } - return -} - -_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { - builder := strings.builder_make(allocator) - loop: #reverse for kv, cur_idx in environment { - eq_idx := strings.index_byte(kv, '=') - assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") - key := kv[:eq_idx] - for old_kv in environment[cur_idx+1:] { - old_key := old_kv[:strings.index_byte(old_kv, '=')] - if key == old_key { - continue loop - } - } - strings.write_string(&builder, kv) - strings.write_byte(&builder, 0) - } - // Note(flysand): In addition to the NUL-terminator for each string, the - // environment block itself is NUL-terminated. - strings.write_byte(&builder, 0) - return strings.to_string(builder) -} 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_js.odin b/core/os/os2/stat_js.odin deleted file mode 100644 index e37864936..000000000 --- a/core/os/os2/stat_js.odin +++ /dev/null @@ -1,25 +0,0 @@ -#+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 -} - -_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 deleted file mode 100644 index dc5bccb54..000000000 --- a/core/os/os2/stat_linux.odin +++ /dev/null @@ -1,79 +0,0 @@ -#+private -package os2 - -import "core:time" -import "base:runtime" -import "core:sys/linux" - -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - impl := (^File_Impl)(f.impl) - return _fstat_internal(impl.fd, allocator) -} - -_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - s: linux.Stat - errno := linux.fstat(fd, &s) - if errno != .NONE { - return {}, _get_platform_error(errno) - } - type := File_Type.Regular - switch s.mode & linux.S_IFMT { - case linux.S_IFBLK: type = .Block_Device - case linux.S_IFCHR: type = .Character_Device - case linux.S_IFDIR: type = .Directory - case linux.S_IFIFO: type = .Named_Pipe - case linux.S_IFLNK: type = .Symlink - case linux.S_IFREG: type = .Regular - case linux.S_IFSOCK: type = .Socket - } - mode := transmute(Permissions)(0o7777 & transmute(u32)s.mode) - - // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time - fi = File_Info { - fullpath = _get_full_path(fd, allocator) or_return, - name = "", - inode = u128(u64(s.ino)), - size = i64(s.size), - mode = mode, - type = type, - modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, - access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, - creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this - } - fi.creation_time = fi.modification_time - _, fi.name = split_path(fi.fullpath) - return -} - -// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath -_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - - fd, errno := linux.open(name_cstr, {}) - if errno != .NONE { - return {}, _get_platform_error(errno) - } - defer linux.close(fd) - return _fstat_internal(fd, allocator) -} - -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - name_cstr := clone_to_cstring(name, temp_allocator) or_return - - fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) - if errno != .NONE { - return {}, _get_platform_error(errno) - } - defer linux.close(fd) - return _fstat_internal(fd, allocator) -} - -_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 deleted file mode 100644 index e401ffe40..000000000 --- a/core/os/os2/stat_posix.odin +++ /dev/null @@ -1,141 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -import "core:sys/posix" -import "core:time" - -internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { - fi.fullpath = fullpath - _, fi.name = split_path(fi.fullpath) - - fi.inode = u128(stat.st_ino) - fi.size = i64(stat.st_size) - - fi.mode = transmute(Permissions)u32(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT)) - - fi.type = .Undetermined - switch { - case posix.S_ISBLK(stat.st_mode): - fi.type = .Block_Device - case posix.S_ISCHR(stat.st_mode): - fi.type = .Character_Device - case posix.S_ISDIR(stat.st_mode): - fi.type = .Directory - case posix.S_ISFIFO(stat.st_mode): - fi.type = .Named_Pipe - case posix.S_ISLNK(stat.st_mode): - fi.type = .Symlink - case posix.S_ISREG(stat.st_mode): - fi.type = .Regular - case posix.S_ISSOCK(stat.st_mode): - fi.type = .Socket - } - - fi.creation_time = timespec_time(stat.st_birthtimespec) - fi.modification_time = timespec_time(stat.st_mtim) - fi.access_time = timespec_time(stat.st_atim) - - timespec_time :: proc(t: posix.timespec) -> time.Time { - return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)} - } - - return -} - -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if f == nil || f.impl == nil { - err = .Invalid_File - return - } - - impl := (^File_Impl)(f.impl) - - stat: posix.stat_t - if posix.fstat(impl.fd, &stat) != .OK { - err = _get_platform_error() - return - } - - fullpath := clone_string(impl.name, allocator) or_return - return internal_stat(stat, fullpath), nil -} - -_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - cname := clone_to_cstring(name, temp_allocator) or_return - - fd := posix.open(cname, {}) - if fd == -1 { - err = _get_platform_error() - return - } - defer posix.close(fd) - - fullpath := _posix_absolute_path(fd, name, allocator) or_return - - stat: posix.stat_t - if posix.stat(fullpath, &stat) != .OK { - err = _get_platform_error() - return - } - - return internal_stat(stat, string(fullpath)), nil -} - -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. - - // NOTE: This might not be correct when given "/symlink/foo.txt", - // you would want that to resolve "/symlink", but not resolve "foo.txt". - - fullpath := clean_path(name, temp_allocator) or_return - assert(len(fullpath) > 0) - switch { - case fullpath[0] == '/': - // nothing. - case fullpath == ".": - fullpath = getwd(temp_allocator) or_return - case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': - fullpath = fullpath[2:] - fallthrough - case: - fullpath = concatenate({ - getwd(temp_allocator) or_return, - "/", - fullpath, - }, temp_allocator) or_return - } - - stat: posix.stat_t - c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return - if posix.lstat(c_fullpath, &stat) != .OK { - err = _get_platform_error() - return - } - - fullpath = clone_string(fullpath, allocator) or_return - return internal_stat(stat, fullpath), nil -} - -_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 deleted file mode 100644 index f15479e22..000000000 --- a/core/os/os2/stat_wasi.odin +++ /dev/null @@ -1,104 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -import "core:sys/wasm/wasi" -import "core:time" - -internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) { - fi.fullpath = fullpath - _, fi.name = split_path(fi.fullpath) - - fi.inode = u128(stat.ino) - fi.size = i64(stat.size) - - switch stat.filetype { - case .BLOCK_DEVICE: fi.type = .Block_Device - case .CHARACTER_DEVICE: fi.type = .Character_Device - case .DIRECTORY: fi.type = .Directory - case .REGULAR_FILE: fi.type = .Regular - case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket - case .SYMBOLIC_LINK: fi.type = .Symlink - case .UNKNOWN: fi.type = .Undetermined - case: fi.type = .Undetermined - } - - fi.creation_time = time.Time{_nsec=i64(stat.ctim)} - fi.modification_time = time.Time{_nsec=i64(stat.mtim)} - fi.access_time = time.Time{_nsec=i64(stat.atim)} - - return -} - -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if f == nil || f.impl == nil { - err = .Invalid_File - return - } - - impl := (^File_Impl)(f.impl) - - stat, _err := wasi.fd_filestat_get(__fd(f)) - if _err != nil { - err = _get_platform_error(_err) - return - } - - fullpath := clone_string(impl.name, allocator) or_return - return internal_stat(stat, fullpath), nil -} - -_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - dir_fd, relative, ok := match_preopen(name) - if !ok { - err = .Invalid_Path - return - } - - stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) - if _err != nil { - err = _get_platform_error(_err) - return - } - - // NOTE: wasi doesn't really do full paths afact. - fullpath := clone_string(name, allocator) or_return - return internal_stat(stat, fullpath), nil -} - -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - if name == "" { - err = .Invalid_Path - return - } - - dir_fd, relative, ok := match_preopen(name) - if !ok { - err = .Invalid_Path - return - } - - stat, _err := wasi.path_filestat_get(dir_fd, {}, relative) - if _err != nil { - err = _get_platform_error(_err) - return - } - - // NOTE: wasi doesn't really do full paths afact. - fullpath := clone_string(name, allocator) or_return - return internal_stat(stat, fullpath), nil -} - -_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_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/temp_file.odin b/core/os/os2/temp_file.odin deleted file mode 100644 index 2c0236428..000000000 --- a/core/os/os2/temp_file.odin +++ /dev/null @@ -1,110 +0,0 @@ -package os2 - -import "base:runtime" - -@(private="file") -MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? - -// Creates a new temperatory file in the directory `dir`. -// -// Opens the file for reading and writing, with `Permissions_Read_Write_All` permissions, and returns the new `^File`. -// The filename is generated by taking a pattern, and adding a randomized string to the end. -// If the pattern includes an "*", the random string replaces the last "*". -// If `dir` is an empty string, `temp_directory()` will be used. -// -// The caller must `close` the file once finished with. -@(require_results) -create_temp_file :: proc(dir, pattern: string, additional_flags: File_Flags = {}) -> (f: ^File, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - dir := dir if dir != "" else temp_directory(temp_allocator) or_return - prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix, temp_allocator) or_return - - rand_buf: [10]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) - - attempts := 0 - for { - name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) - f, err = open(name, {.Read, .Write, .Create, .Excl} + additional_flags, Permissions_Read_Write_All) - if err == .Exist { - close(f) - attempts += 1 - if attempts < MAX_ATTEMPTS { - continue - } - return nil, err - } - return f, err - } -} - -mkdir_temp :: make_directory_temp -// Creates a new temporary directory in the directory `dir`, and returns the path of the new directory. -// -// The directory name is generated by taking a pattern, and adding a randomized string to the end. -// If the pattern includes an "*", the random string replaces the last "*". -// If `dir` is an empty tring, `temp_directory()` will be used. -@(require_results) -make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - dir := dir if dir != "" else temp_directory(temp_allocator) or_return - prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix, temp_allocator) or_return - - rand_buf: [10]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) - - attempts := 0 - for { - name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) - err = make_directory(name, 0o700) - if err == nil { - return clone_string(name, allocator) - } - if err == .Exist { - attempts += 1 - if attempts < MAX_ATTEMPTS { - continue - } - return "", err - } - if err == .Not_Exist { - if _, serr := stat(dir, temp_allocator); serr == .Not_Exist { - return "", serr - } - } - return "", err - } - -} - -temp_dir :: temp_directory - -/* - Returns the default directory to use for temporary files. - - On Unix systems, it typically returns $TMPDIR if non-empty, otherwlse `/tmp`. - On Windows, it uses `GetTempPathW`, returning the first non-empty value from one of the following: - * `%TMP%` - * `%TEMP%` - * `%USERPROFILE %` - * or the Windows directory - See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw for more information. - On wasi, it returns `/tmp`. -*/ -@(require_results) -temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { - return _temp_dir(allocator) -} - - - -@(private="file") -temp_join_path :: proc(dir, name: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { - return concatenate({dir, name}, allocator) - } - - return concatenate({dir, Path_Separator_String, name}, allocator) -} diff --git a/core/os/os2/temp_file_js.odin b/core/os/os2/temp_file_js.odin deleted file mode 100644 index e1f2b3d95..000000000 --- a/core/os/os2/temp_file_js.odin +++ /dev/null @@ -1,9 +0,0 @@ -#+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 diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin deleted file mode 100644 index 310720cbe..000000000 --- a/core/os/os2/temp_file_linux.odin +++ /dev/null @@ -1,13 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - tmpdir := get_env("TMPDIR", temp_allocator) - if tmpdir == "" { - tmpdir = "/tmp" - } - return clone_string(tmpdir, allocator) -} diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin deleted file mode 100644 index b44ea13a7..000000000 --- a/core/os/os2/temp_file_posix.odin +++ /dev/null @@ -1,20 +0,0 @@ -#+private -#+build darwin, netbsd, freebsd, openbsd -package os2 - -import "base:runtime" - -@(require) -import "core:sys/posix" - -_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - if tmp, ok := _lookup_env("TMPDIR", allocator); ok { - return tmp, nil - } - - when #defined(posix.P_tmpdir) { - return clone_string(posix.P_tmpdir, allocator) - } - - return clone_string("/tmp/", allocator) -} diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/os2/temp_file_wasi.odin deleted file mode 100644 index d5628d300..000000000 --- a/core/os/os2/temp_file_wasi.odin +++ /dev/null @@ -1,9 +0,0 @@ -#+private -package os2 - -import "base:runtime" - -_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - // NOTE: requires user to add /tmp to their preopen dirs, no standard way exists. - return clone_string("/tmp", allocator) -} diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin deleted file mode 100644 index 91ea284a1..000000000 --- a/core/os/os2/temp_file_windows.odin +++ /dev/null @@ -1,23 +0,0 @@ -#+private -package os2 - -import "base:runtime" -import win32 "core:sys/windows" - -_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - n := win32.GetTempPathW(0, nil) - if n == 0 { - return "", nil - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - b := make([]u16, max(win32.MAX_PATH, n), temp_allocator) - n = win32.GetTempPathW(u32(len(b)), cstring16(raw_data(b))) - - if n == 3 && b[1] == ':' && b[2] == '\\' { - - } else if n > 0 && b[n-1] == '\\' { - n -= 1 - } - return win32_utf16_to_utf8(string16(b[:n]), allocator) -} diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin deleted file mode 100644 index e2a4ec4d0..000000000 --- a/core/os/os2/user.odin +++ /dev/null @@ -1,149 +0,0 @@ -package os2 - -import "base:runtime" - -// ``` -// Windows: C:\Users\Alice -// macOS: /Users/Alice -// Linux: /home/alice -// ``` -@(require_results) -user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_home_dir(allocator) -} - -// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches -// -// Sometimes deleted for system maintenance -// -// ``` -// Windows: C:\Users\Alice\AppData\Local -// macOS: /Users/Alice/Library/Caches -// Linux: /home/alice/.cache -// ``` -@(require_results) -user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_cache_dir(allocator) -} - -// User-hidden application data -// -// ``` -// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) -// macOS: /Users/Alice/Library/Application Support -// Linux: /home/alice/.local/share -// ``` -// -// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* -@(require_results) -user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { - return _user_data_dir(allocator, roaming) -} - -// Non-essential application data, e.g. history, ui layout state -// -// ``` -// Windows: C:\Users\Alice\AppData\Local -// macOS: /Users/Alice/Library/Application Support -// Linux: /home/alice/.local/state -// ``` -@(require_results) -user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_state_dir(allocator) -} - -// Application log files -// -// ``` -// Windows: C:\Users\Alice\AppData\Local -// macOS: /Users/Alice/Library/Logs -// Linux: /home/alice/.local/state -// ``` -@(require_results) -user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_log_dir(allocator) -} - -// Application settings/preferences -// -// ``` -// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) -// macOS: /Users/Alice/Library/Application Support -// Linux: /home/alice/.config -// ``` -// -// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* -@(require_results) -user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { - return _user_config_dir(allocator, roaming) -} - -// ``` -// Windows: C:\Users\Alice\Music -// macOS: /Users/Alice/Music -// Linux: /home/alice/Music -// ``` -@(require_results) -user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_music_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Desktop -// macOS: /Users/Alice/Desktop -// Linux: /home/alice/Desktop -// ``` -@(require_results) -user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_desktop_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Documents -// macOS: /Users/Alice/Documents -// Linux: /home/alice/Documents -// ``` -@(require_results) -user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_documents_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Downloads -// macOS: /Users/Alice/Downloads -// Linux: /home/alice/Downloads -// ``` -@(require_results) -user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_downloads_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Pictures -// macOS: /Users/Alice/Pictures -// Linux: /home/alice/Pictures -// ``` -@(require_results) -user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_pictures_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Public -// macOS: /Users/Alice/Public -// Linux: /home/alice/Public -// ``` -@(require_results) -user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_public_dir(allocator) -} - -// ``` -// Windows: C:\Users\Alice\Videos -// macOS: /Users/Alice/Movies -// Linux: /home/alice/Videos -// ``` -@(require_results) -user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _user_videos_dir(allocator) -} \ No newline at end of file diff --git a/core/os/os2/user_posix.odin b/core/os/os2/user_posix.odin deleted file mode 100644 index fa173f129..000000000 --- a/core/os/os2/user_posix.odin +++ /dev/null @@ -1,176 +0,0 @@ -#+build !windows -package os2 - -import "base:intrinsics" -import "base:runtime" -import "core:strings" - -_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Library/Caches", allocator) - case: // Unix - return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator) - } -} - -_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Library/Application Support", allocator) - case: // Unix - return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator) - } -} - -_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Library/Application Support", allocator) - case: // Unix - return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) - } -} - -_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Library/Logs", allocator) - case: // Unix - return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) - } -} - -_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Library/Application Support", allocator) - case: // Unix - return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator) - } -} - -_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Music", allocator) - case: // Unix - return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator) - } -} - -_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Desktop", allocator) - case: // Unix - return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator) - } -} - -_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Documents", allocator) - case: // Unix - return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator) - } -} - -_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Downloads", allocator) - case: // Unix - return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator) - } -} - -_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Pictures", allocator) - case: // Unix - return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator) - } -} - -_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Public", allocator) - case: // Unix - return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator) - } -} - -_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - #partial switch ODIN_OS { - case .Darwin: - return _xdg_lookup("", "/Movies", allocator) - case: // Unix - return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator) - } -} - -_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - if v := get_env("HOME", allocator); v != "" { - return v, nil - } - err = .No_HOME_Variable - return -} - -_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - - if xdg_key == "" { // Darwin doesn't have XDG paths. - dir = get_env("HOME", temp_allocator) - if dir == "" { - err = .No_HOME_Variable - return - } - return concatenate({dir, fallback_suffix}, allocator) - } else { - if strings.ends_with(xdg_key, "_DIR") { - dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return - } else { - dir = get_env(xdg_key, allocator) - } - - if dir == "" { - dir = get_env("HOME", temp_allocator) - if dir == "" { - err = .No_HOME_Variable - return - } - dir = concatenate({dir, fallback_suffix}, allocator) or_return - } - return - } -} - -// If `/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""` -_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - config_dir := user_config_dir(temp_allocator) or_return - user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return - content := read_entire_file(user_dirs_path, temp_allocator) or_return - - xdg_dirs := string(content) - for line in strings.split_lines_iterator(&xdg_dirs) { - if len(line) > 0 && line[0] == '#' { - continue - } - - equals := strings.index(line, "=") - if equals > -1 { - if line[:equals] == xdg_key { - // Unquote to return a bare path string as we do on Windows - val := strings.trim(line[equals+1:], "\"") - return replace_environment_placeholders(val, allocator), nil - } - } - } - return -} \ No newline at end of file diff --git a/core/os/os2/user_windows.odin b/core/os/os2/user_windows.odin deleted file mode 100644 index 75d0ba6ac..000000000 --- a/core/os/os2/user_windows.odin +++ /dev/null @@ -1,78 +0,0 @@ -package os2 - -import "base:runtime" -@(require) import win32 "core:sys/windows" - -_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_LocalAppData - return _get_known_folder_path(&guid, allocator) -} - -_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) { - guid := win32.FOLDERID_LocalAppData - if roaming { - guid = win32.FOLDERID_RoamingAppData - } - return _get_known_folder_path(&guid, allocator) -} - -_user_config_dir :: _local_appdata_or_roaming -_user_data_dir :: _local_appdata_or_roaming - -_user_state_dir :: _local_appdata -_user_log_dir :: _local_appdata -_user_cache_dir :: _local_appdata - -_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Profile - return _get_known_folder_path(&guid, allocator) -} - -_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Music - return _get_known_folder_path(&guid, allocator) -} - -_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Desktop - return _get_known_folder_path(&guid, allocator) -} - -_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Documents - return _get_known_folder_path(&guid, allocator) -} - -_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Downloads - return _get_known_folder_path(&guid, allocator) -} - -_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Pictures - return _get_known_folder_path(&guid, allocator) -} - -_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Public - return _get_known_folder_path(&guid, allocator) -} - -_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - guid := win32.FOLDERID_Videos - return _get_known_folder_path(&guid, allocator) -} - -_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) { - // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath - // See also `known_folders.odin` in `core:sys/windows` for the GUIDs. - path_w: win32.LPWSTR - res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w) - defer win32.CoTaskMemFree(path_w) - - if res != 0 { - return "", .Invalid_Path - } - - return win32_wstring_to_utf8(cstring16(path_w), allocator) -} \ No newline at end of file diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin deleted file mode 100644 index 92a636255..000000000 --- a/core/os/os_darwin.odin +++ /dev/null @@ -1,1348 +0,0 @@ -package os - -foreign import dl "system:dl" -foreign import libc "system:System" -foreign import pthread "system:System" - -import "base:runtime" -import "core:strings" -import "core:c" - -Handle :: distinct i32 -File_Time :: distinct u64 - -INVALID_HANDLE :: ~Handle(0) - -_Platform_Error :: enum i32 { - NONE = 0, - EPERM = 1, /* Operation not permitted */ - ENOENT = 2, /* No such file or directory */ - ESRCH = 3, /* No such process */ - EINTR = 4, /* Interrupted system call */ - EIO = 5, /* Input/output error */ - ENXIO = 6, /* Device not configured */ - E2BIG = 7, /* Argument list too long */ - ENOEXEC = 8, /* Exec format error */ - EBADF = 9, /* Bad file descriptor */ - ECHILD = 10, /* No child processes */ - EDEADLK = 11, /* Resource deadlock avoided */ - ENOMEM = 12, /* Cannot allocate memory */ - EACCES = 13, /* Permission denied */ - EFAULT = 14, /* Bad address */ - ENOTBLK = 15, /* Block device required */ - EBUSY = 16, /* Device / Resource busy */ - EEXIST = 17, /* File exists */ - EXDEV = 18, /* Cross-device link */ - ENODEV = 19, /* Operation not supported by device */ - ENOTDIR = 20, /* Not a directory */ - EISDIR = 21, /* Is a directory */ - EINVAL = 22, /* Invalid argument */ - ENFILE = 23, /* Too many open files in system */ - EMFILE = 24, /* Too many open files */ - ENOTTY = 25, /* Inappropriate ioctl for device */ - ETXTBSY = 26, /* Text file busy */ - EFBIG = 27, /* File too large */ - ENOSPC = 28, /* No space left on device */ - ESPIPE = 29, /* Illegal seek */ - EROFS = 30, /* Read-only file system */ - EMLINK = 31, /* Too many links */ - EPIPE = 32, /* Broken pipe */ - - /* math software */ - EDOM = 33, /* Numerical argument out of domain */ - ERANGE = 34, /* Result too large */ - - /* non-blocking and interrupt i/o */ - EAGAIN = 35, /* Resource temporarily unavailable */ - EWOULDBLOCK = EAGAIN, /* Operation would block */ - EINPROGRESS = 36, /* Operation now in progress */ - EALREADY = 37, /* Operation already in progress */ - - /* ipc/network software -- argument errors */ - ENOTSOCK = 38, /* Socket operation on non-socket */ - EDESTADDRREQ = 39, /* Destination address required */ - EMSGSIZE = 40, /* Message too long */ - EPROTOTYPE = 41, /* Protocol wrong type for socket */ - ENOPROTOOPT = 42, /* Protocol not available */ - EPROTONOSUPPORT = 43, /* Protocol not supported */ - ESOCKTNOSUPPORT = 44, /* Socket type not supported */ - ENOTSUP = 45, /* Operation not supported */ - EOPNOTSUPP = ENOTSUP, - EPFNOSUPPORT = 46, /* Protocol family not supported */ - EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ - EADDRINUSE = 48, /* Address already in use */ - EADDRNOTAVAIL = 49, /* Can't assign requested address */ - - /* ipc/network software -- operational errors */ - ENETDOWN = 50, /* Network is down */ - ENETUNREACH = 51, /* Network is unreachable */ - ENETRESET = 52, /* Network dropped connection on reset */ - ECONNABORTED = 53, /* Software caused connection abort */ - ECONNRESET = 54, /* Connection reset by peer */ - ENOBUFS = 55, /* No buffer space available */ - EISCONN = 56, /* Socket is already connected */ - ENOTCONN = 57, /* Socket is not connected */ - ESHUTDOWN = 58, /* Can't send after socket shutdown */ - ETOOMANYREFS = 59, /* Too many references: can't splice */ - ETIMEDOUT = 60, /* Operation timed out */ - ECONNREFUSED = 61, /* Connection refused */ - - ELOOP = 62, /* Too many levels of symbolic links */ - ENAMETOOLONG = 63, /* File name too long */ - - /* should be rearranged */ - EHOSTDOWN = 64, /* Host is down */ - EHOSTUNREACH = 65, /* No route to host */ - ENOTEMPTY = 66, /* Directory not empty */ - - /* quotas & mush */ - EPROCLIM = 67, /* Too many processes */ - EUSERS = 68, /* Too many users */ - EDQUOT = 69, /* Disc quota exceeded */ - - /* Network File System */ - ESTALE = 70, /* Stale NFS file handle */ - EREMOTE = 71, /* Too many levels of remote in path */ - EBADRPC = 72, /* RPC struct is bad */ - ERPCMISMATCH = 73, /* RPC version wrong */ - EPROGUNAVAIL = 74, /* RPC prog. not avail */ - EPROGMISMATCH = 75, /* Program version wrong */ - EPROCUNAVAIL = 76, /* Bad procedure for program */ - - ENOLCK = 77, /* No locks available */ - ENOSYS = 78, /* Function not implemented */ - - EFTYPE = 79, /* Inappropriate file type or format */ - EAUTH = 80, /* Authentication error */ - ENEEDAUTH = 81, /* Need authenticator */ - - /* Intelligent device errors */ - EPWROFF = 82, /* Device power is off */ - EDEVERR = 83, /* Device error, e.g. paper out */ - EOVERFLOW = 84, /* Value too large to be stored in data type */ - - /* Program loading errors */ - EBADEXEC = 85, /* Bad executable */ - EBADARCH = 86, /* Bad CPU type in executable */ - ESHLIBVERS = 87, /* Shared library version mismatch */ - EBADMACHO = 88, /* Malformed Macho file */ - - ECANCELED = 89, /* Operation canceled */ - - EIDRM = 90, /* Identifier removed */ - ENOMSG = 91, /* No message of desired type */ - EILSEQ = 92, /* Illegal byte sequence */ - ENOATTR = 93, /* Attribute not found */ - - EBADMSG = 94, /* Bad message */ - EMULTIHOP = 95, /* Reserved */ - ENODATA = 96, /* No message available on STREAM */ - ENOLINK = 97, /* Reserved */ - ENOSR = 98, /* No STREAM resources */ - ENOSTR = 99, /* Not a STREAM */ - EPROTO = 100, /* Protocol error */ - ETIME = 101, /* STREAM ioctl timeout */ - - ENOPOLICY = 103, /* No such policy registered */ - - ENOTRECOVERABLE = 104, /* State not recoverable */ - EOWNERDEAD = 105, /* Previous owner died */ - - EQFULL = 106, /* Interface output queue is full */ - ELAST = 106, /* Must be equal largest errno */ -} - -EPERM :: _Platform_Error.EPERM -ENOENT :: _Platform_Error.ENOENT -ESRCH :: _Platform_Error.ESRCH -EINTR :: _Platform_Error.EINTR -EIO :: _Platform_Error.EIO -ENXIO :: _Platform_Error.ENXIO -E2BIG :: _Platform_Error.E2BIG -ENOEXEC :: _Platform_Error.ENOEXEC -EBADF :: _Platform_Error.EBADF -ECHILD :: _Platform_Error.ECHILD -EDEADLK :: _Platform_Error.EDEADLK -ENOMEM :: _Platform_Error.ENOMEM -EACCES :: _Platform_Error.EACCES -EFAULT :: _Platform_Error.EFAULT -ENOTBLK :: _Platform_Error.ENOTBLK -EBUSY :: _Platform_Error.EBUSY -EEXIST :: _Platform_Error.EEXIST -EXDEV :: _Platform_Error.EXDEV -ENODEV :: _Platform_Error.ENODEV -ENOTDIR :: _Platform_Error.ENOTDIR -EISDIR :: _Platform_Error.EISDIR -EINVAL :: _Platform_Error.EINVAL -ENFILE :: _Platform_Error.ENFILE -EMFILE :: _Platform_Error.EMFILE -ENOTTY :: _Platform_Error.ENOTTY -ETXTBSY :: _Platform_Error.ETXTBSY -EFBIG :: _Platform_Error.EFBIG -ENOSPC :: _Platform_Error.ENOSPC -ESPIPE :: _Platform_Error.ESPIPE -EROFS :: _Platform_Error.EROFS -EMLINK :: _Platform_Error.EMLINK -EPIPE :: _Platform_Error.EPIPE - -/* math software */ -EDOM :: _Platform_Error.EDOM -ERANGE :: _Platform_Error.ERANGE - -/* non-blocking and interrupt i/o */ -EAGAIN :: _Platform_Error.EAGAIN -EWOULDBLOCK :: _Platform_Error.EWOULDBLOCK -EINPROGRESS :: _Platform_Error.EINPROGRESS -EALREADY :: _Platform_Error.EALREADY - -/* ipc/network software -- argument errors */ -ENOTSOCK :: _Platform_Error.ENOTSOCK -EDESTADDRREQ :: _Platform_Error.EDESTADDRREQ -EMSGSIZE :: _Platform_Error.EMSGSIZE -EPROTOTYPE :: _Platform_Error.EPROTOTYPE -ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT -EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT -ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT -ENOTSUP :: _Platform_Error.ENOTSUP -EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP -EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT -EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT -EADDRINUSE :: _Platform_Error.EADDRINUSE -EADDRNOTAVAIL :: _Platform_Error.EADDRNOTAVAIL - -/* ipc/network software -- operational errors */ -ENETDOWN :: _Platform_Error.ENETDOWN -ENETUNREACH :: _Platform_Error.ENETUNREACH -ENETRESET :: _Platform_Error.ENETRESET -ECONNABORTED :: _Platform_Error.ECONNABORTED -ECONNRESET :: _Platform_Error.ECONNRESET -ENOBUFS :: _Platform_Error.ENOBUFS -EISCONN :: _Platform_Error.EISCONN -ENOTCONN :: _Platform_Error.ENOTCONN -ESHUTDOWN :: _Platform_Error.ESHUTDOWN -ETOOMANYREFS :: _Platform_Error.ETOOMANYREFS -ETIMEDOUT :: _Platform_Error.ETIMEDOUT -ECONNREFUSED :: _Platform_Error.ECONNREFUSED - -ELOOP :: _Platform_Error.ELOOP -ENAMETOOLONG :: _Platform_Error.ENAMETOOLONG - -/* should be rearranged */ -EHOSTDOWN :: _Platform_Error.EHOSTDOWN -EHOSTUNREACH :: _Platform_Error.EHOSTUNREACH -ENOTEMPTY :: _Platform_Error.ENOTEMPTY - -/* quotas & mush */ -EPROCLIM :: _Platform_Error.EPROCLIM -EUSERS :: _Platform_Error.EUSERS -EDQUOT :: _Platform_Error.EDQUOT - -/* Network File System */ -ESTALE :: _Platform_Error.ESTALE -EREMOTE :: _Platform_Error.EREMOTE -EBADRPC :: _Platform_Error.EBADRPC -ERPCMISMATCH :: _Platform_Error.ERPCMISMATCH -EPROGUNAVAIL :: _Platform_Error.EPROGUNAVAIL -EPROGMISMATCH :: _Platform_Error.EPROGMISMATCH -EPROCUNAVAIL :: _Platform_Error.EPROCUNAVAIL - -ENOLCK :: _Platform_Error.ENOLCK -ENOSYS :: _Platform_Error.ENOSYS - -EFTYPE :: _Platform_Error.EFTYPE -EAUTH :: _Platform_Error.EAUTH -ENEEDAUTH :: _Platform_Error.ENEEDAUTH - -/* Intelligent device errors */ -EPWROFF :: _Platform_Error.EPWROFF -EDEVERR :: _Platform_Error.EDEVERR -EOVERFLOW :: _Platform_Error.EOVERFLOW - -/* Program loading errors */ -EBADEXEC :: _Platform_Error.EBADEXEC -EBADARCH :: _Platform_Error.EBADARCH -ESHLIBVERS :: _Platform_Error.ESHLIBVERS -EBADMACHO :: _Platform_Error.EBADMACHO - -ECANCELED :: _Platform_Error.ECANCELED - -EIDRM :: _Platform_Error.EIDRM -ENOMSG :: _Platform_Error.ENOMSG -EILSEQ :: _Platform_Error.EILSEQ -ENOATTR :: _Platform_Error.ENOATTR - -EBADMSG :: _Platform_Error.EBADMSG -EMULTIHOP :: _Platform_Error.EMULTIHOP -ENODATA :: _Platform_Error.ENODATA -ENOLINK :: _Platform_Error.ENOLINK -ENOSR :: _Platform_Error.ENOSR -ENOSTR :: _Platform_Error.ENOSTR -EPROTO :: _Platform_Error.EPROTO -ETIME :: _Platform_Error.ETIME - -ENOPOLICY :: _Platform_Error.ENOPOLICY - -ENOTRECOVERABLE :: _Platform_Error.ENOTRECOVERABLE -EOWNERDEAD :: _Platform_Error.EOWNERDEAD - -EQFULL :: _Platform_Error.EQFULL -ELAST :: _Platform_Error.ELAST - - -O_RDONLY :: 0x0000 -O_WRONLY :: 0x0001 -O_RDWR :: 0x0002 -O_CREATE :: 0x0200 -O_EXCL :: 0x0800 -O_NOCTTY :: 0 -O_TRUNC :: 0x0400 -O_NONBLOCK :: 0x0004 -O_APPEND :: 0x0008 -O_SYNC :: 0x0080 -O_ASYNC :: 0x0040 -O_CLOEXEC :: 0x1000000 - -SEEK_DATA :: 3 -SEEK_HOLE :: 4 -SEEK_MAX :: SEEK_HOLE - - - -// NOTE(zangent): These are OS specific! -// Do not mix these up! -RTLD_LAZY :: 0x1 -RTLD_NOW :: 0x2 -RTLD_LOCAL :: 0x4 -RTLD_GLOBAL :: 0x8 -RTLD_NODELETE :: 0x80 -RTLD_NOLOAD :: 0x10 -RTLD_FIRST :: 0x100 - -SOL_SOCKET :: 0xFFFF - -SOCK_STREAM :: 1 -SOCK_DGRAM :: 2 -SOCK_RAW :: 3 -SOCK_RDM :: 4 -SOCK_SEQPACKET :: 5 - -SO_DEBUG :: 0x0001 -SO_ACCEPTCONN :: 0x0002 -SO_REUSEADDR :: 0x0004 -SO_KEEPALIVE :: 0x0008 -SO_DONTROUTE :: 0x0010 -SO_BROADCAST :: 0x0020 -SO_USELOOPBACK :: 0x0040 -SO_LINGER :: 0x0080 -SO_OOBINLINE :: 0x0100 -SO_REUSEPORT :: 0x0200 -SO_TIMESTAMP :: 0x0400 - -SO_DONTTRUNC :: 0x2000 -SO_WANTMORE :: 0x4000 -SO_WANTOOBFLAG :: 0x8000 -SO_SNDBUF :: 0x1001 -SO_RCVBUF :: 0x1002 -SO_SNDLOWAT :: 0x1003 -SO_RCVLOWAT :: 0x1004 -SO_SNDTIMEO :: 0x1005 -SO_RCVTIMEO :: 0x1006 -SO_ERROR :: 0x1007 -SO_TYPE :: 0x1008 -SO_PRIVSTATE :: 0x1009 -SO_NREAD :: 0x1020 -SO_NKE :: 0x1021 - -AF_UNSPEC :: 0 -AF_LOCAL :: 1 -AF_UNIX :: AF_LOCAL -AF_INET :: 2 -AF_IMPLINK :: 3 -AF_PUP :: 4 -AF_CHAOS :: 5 -AF_NS :: 6 -AF_ISO :: 7 -AF_OSI :: AF_ISO -AF_ECMA :: 8 -AF_DATAKIT :: 9 -AF_CCITT :: 10 -AF_SNA :: 11 -AF_DECnet :: 12 -AF_DLI :: 13 -AF_LAT :: 14 -AF_HYLINK :: 15 -AF_APPLETALK :: 16 -AF_ROUTE :: 17 -AF_LINK :: 18 -pseudo_AF_XTP :: 19 -AF_COIP :: 20 -AF_CNT :: 21 -pseudo_AF_RTIP :: 22 -AF_IPX :: 23 -AF_SIP :: 24 -pseudo_AF_PIP :: 25 -pseudo_AF_BLUE :: 26 -AF_NDRV :: 27 -AF_ISDN :: 28 -AF_E164 :: AF_ISDN -pseudo_AF_KEY :: 29 -AF_INET6 :: 30 -AF_NATM :: 31 -AF_SYSTEM :: 32 -AF_NETBIOS :: 33 -AF_PPP :: 34 - -TCP_NODELAY :: 0x01 -TCP_MAXSEG :: 0x02 -TCP_NOPUSH :: 0x04 -TCP_NOOPT :: 0x08 - -IPPROTO_ICMP :: 1 -IPPROTO_TCP :: 6 -IPPROTO_UDP :: 17 - -SHUT_RD :: 0 -SHUT_WR :: 1 -SHUT_RDWR :: 2 - -F_GETFL: int : 3 /* Get file flags */ -F_SETFL: int : 4 /* Set file flags */ - -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - -Unix_File_Time :: struct { - seconds: i64, - nanoseconds: i64, -} - -OS_Stat :: struct { - device_id: i32, // ID of device containing file - mode: u16, // Mode of the file - nlink: u16, // Number of hard links - serial: u64, // File serial number - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - rdev: i32, // Device ID, if device - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - created: Unix_File_Time, // Time of creation - - size: i64, // Size of the file, in bytes - blocks: i64, // Number of blocks allocated for the file - block_size: i32, // Optimal blocksize for I/O - flags: u32, // User-defined flags for the file - gen_num: u32, // File generation number ..? - _spare: i32, // RESERVED - _reserve1, - _reserve2: i64, // RESERVED -} - -DARWIN_MAXPATHLEN :: 1024 -Dirent :: struct { - ino: u64, - off: u64, - reclen: u16, - namlen: u16, - type: u8, - name: [DARWIN_MAXPATHLEN]byte, -} - -Dir :: distinct rawptr // DIR* - -ADDRESS_FAMILY :: c.char -SOCKADDR :: struct #packed { - len: c.char, - family: ADDRESS_FAMILY, - sa_data: [14]c.char, -} - -SOCKADDR_STORAGE_LH :: struct #packed { - len: c.char, - family: ADDRESS_FAMILY, - __ss_pad1: [6]c.char, - __ss_align: i64, - __ss_pad2: [112]c.char, -} - -sockaddr_in :: struct #packed { - sin_len: c.char, - sin_family: ADDRESS_FAMILY, - sin_port: u16be, - sin_addr: in_addr, - sin_zero: [8]c.char, -} - -sockaddr_in6 :: struct #packed { - sin6_len: c.char, - sin6_family: ADDRESS_FAMILY, - sin6_port: u16be, - sin6_flowinfo: c.uint, - sin6_addr: in6_addr, - sin6_scope_id: c.uint, -} - -in_addr :: struct #packed { - s_addr: u32, -} - -in6_addr :: struct #packed { - s6_addr: [16]u8, -} - -// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1025-L1027 -// Prevent the raising of SIGPIPE on writing to a closed network socket. -MSG_NOSIGNAL :: 0x80000 - -SIOCGIFFLAG :: enum c.int { - UP = 0, /* Interface is up. */ - BROADCAST = 1, /* Broadcast address valid. */ - DEBUG = 2, /* Turn on debugging. */ - LOOPBACK = 3, /* Is a loopback net. */ - POINT_TO_POINT = 4, /* Interface is point-to-point link. */ - NO_TRAILERS = 5, /* Avoid use of trailers. */ - RUNNING = 6, /* Resources allocated. */ - NOARP = 7, /* No address resolution protocol. */ - PROMISC = 8, /* Receive all packets. */ - ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */ -} -SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] - -ifaddrs :: struct { - next: ^ifaddrs, - name: cstring, - flags: SIOCGIFFLAGS, - address: ^SOCKADDR, - netmask: ^SOCKADDR, - broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address - data: rawptr, // Address-specific data. -} - -Timeval :: struct { - seconds: i64, - microseconds: int, -} - -Linger :: struct { - onoff: int, - linger: int, -} - -Socket :: distinct int -socklen_t :: c.int - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - -// Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - -// Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - -@(require_results) S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } -@(require_results) S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } -@(require_results) S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } -@(require_results) S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } -@(require_results) S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } -@(require_results) S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } -@(require_results) S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } - -R_OK :: 4 // Test for read permission -W_OK :: 2 // Test for write permission -X_OK :: 1 // Test for execute permission -F_OK :: 0 // Test for file existance - -F_GETPATH :: 50 // return the full path of the fd - -foreign libc { - @(link_name="__error") __error :: proc() -> ^c.int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg mode: ..u16) -> Handle --- - @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- - @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- - @(link_name="pread") _unix_pread :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- - @(link_name="pwrite") _unix_pwrite :: proc(handle: Handle, buffer: rawptr, count: c.size_t, offset: i64) -> int --- - @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: c.int) -> int --- - @(link_name="gettid") _unix_gettid :: proc() -> u64 --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 --- - @(link_name="stat64") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- - @(link_name="lstat64") _unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- - @(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="dup") _unix_dup :: proc(handle: Handle) -> Handle --- - - @(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- - @(link_name="readdir_r$INODE64") _unix_readdir_r_amd64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - @(link_name="fdopendir") _unix_fdopendir_arm64 :: proc(fd: Handle) -> Dir --- - @(link_name="readdir_r") _unix_readdir_r_arm64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - - @(link_name="__fcntl") _unix__fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- - - @(link_name="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int --- - @(link_name="remove") _unix_remove :: proc(path: cstring) -> c.int --- - - @(link_name="fchmod") _unix_fchmod :: proc(fd: Handle, mode: u16) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: int) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="unsetenv") _unix_unsetenv :: proc(cstring) -> c.int --- - @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- - - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(buf: cstring, mode: u16) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - - @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- - @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - - @(link_name="socket") _unix_socket :: proc(domain: c.int, type: c.int, protocol: c.int) -> c.int --- - @(link_name="listen") _unix_listen :: proc(socket: c.int, backlog: c.int) -> c.int --- - @(link_name="accept") _unix_accept :: proc(socket: c.int, addr: rawptr, addr_len: rawptr) -> c.int --- - @(link_name="connect") _unix_connect :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- - @(link_name="bind") _unix_bind :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- - @(link_name="setsockopt") _unix_setsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: socklen_t) -> c.int --- - @(link_name="getsockopt") _unix_getsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: ^socklen_t) -> c.int --- - @(link_name="recvfrom") _unix_recvfrom :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- - @(link_name="recv") _unix_recv :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- - @(link_name="sendto") _unix_sendto :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- - @(link_name="send") _unix_send :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- - @(link_name="shutdown") _unix_shutdown :: proc(socket: c.int, how: c.int) -> c.int --- - - @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- - @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- -} - -when ODIN_ARCH != .arm64 { - _unix_fdopendir :: proc {_unix_fdopendir_amd64} - _unix_readdir_r :: proc {_unix_readdir_r_amd64} -} else { - _unix_fdopendir :: proc {_unix_fdopendir_arm64} - _unix_readdir_r :: proc {_unix_readdir_r_arm64} -} - -foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- -} - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - return Platform_Error(__error()^) -} - -@(require_results) -get_last_error_string :: proc() -> string { - return string(_darwin_string_error(__error()^)) -} - - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (handle: Handle, err: Error) { - isDir := is_dir_path(path) - flags := flags - if isDir { - /* - @INFO(Platin): To make it impossible to use the wrong flag for dir's - as you can't write to a dir only read which makes it fail to open - */ - flags = O_RDONLY - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle = _unix_open(cstr, i32(flags), u16(mode)) - if handle == INVALID_HANDLE { - err = get_last_error() - return - } - - return -} - -fchmod :: proc(fd: Handle, mode: u16) -> Error { - return cast(Platform_Error)_unix_fchmod(fd, mode) -} - -close :: proc(fd: Handle) -> Error { - return cast(Platform_Error)_unix_close(fd) -} - -// If you read or write more than `SSIZE_MAX` bytes, most darwin implementations will return `EINVAL` -// but it is really implementation defined. `SSIZE_MAX` is also implementation defined but usually -// the max of an i32 on Darwin. -// In practice a read/write call would probably never read/write these big buffers all at once, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - - bytes_written := _unix_write(fd, raw_data(data), to_write) - if bytes_written < 0 { - return -1, get_last_error() - } - return bytes_written, nil -} - -read :: proc(fd: Handle, data: []u8) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(c.size_t(len(data)), MAX_RW) - - bytes_read := _unix_read(fd, raw_data(data), to_read) - if bytes_read < 0 { - return -1, get_last_error() - } - return bytes_read, nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(c.size_t(len(data)), MAX_RW) - - bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) - if bytes_read < 0 { - return -1, get_last_error() - } - return bytes_read, nil -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - - bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) - if bytes_written < 0 { - return -1, get_last_error() - } - return bytes_written, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - assert(fd != -1) - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - - final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) - if final_offset == -1 { - errno := get_last_error() - switch errno { - case .EINVAL: - return 0, .Invalid_Offset - } - return 0, errno - } - return final_offset, nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (i64, Error) { - prev, _ := seek(fd, 0, SEEK_CUR) - size, err := seek(fd, 0, SEEK_END) - seek(fd, prev, SEEK_SET) - return i64(size), err -} - - - -// NOTE(bill): Uses startup to initialize it -stdin: Handle = 0 // get_std_handle(win32.STD_INPUT_HANDLE); -stdout: Handle = 1 // get_std_handle(win32.STD_OUTPUT_HANDLE); -stderr: Handle = 2 // get_std_handle(win32.STD_ERROR_HANDLE); - -@(require_results) -last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { - s := _fstat(fd) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { - s := _stat(name) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -@(require_results) -is_file_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_file_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISREG(s.mode) -} - - -@(require_results) -is_dir_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -@(require_results) -is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -is_file :: proc {is_file_path, is_file_handle} -is_dir :: proc {is_dir_path, is_dir_handle} - -@(require_results) -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cpath := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_access(cpath, O_RDONLY) - return res == 0 -} - -rename :: proc(old: string, new: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - old_cstr := strings.clone_to_cstring(old, context.temp_allocator) - new_cstr := strings.clone_to_cstring(new, context.temp_allocator) - return _unix_rename(old_cstr, new_cstr) != -1 -} - -remove :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_remove(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -@(private, require_results) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - s: OS_Stat - result := _unix_stat(cstr, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - s: OS_Stat - result := _unix_lstat(cstr, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - s: OS_Stat - result := _unix_fstat(fd, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - - if result == nil { - end_of_stream = true - return - } - end_of_stream = false - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = 256 - buf := make([]byte, bufsz) - for { - rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) - if rc == -1 { - delete(buf) - return "", get_last_error() - } else if rc == int(bufsz) { - // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? - bufsz *= 2 - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup := _unix_dup(fd) - if dup == -1 { - return INVALID_HANDLE, get_last_error() - } - return dup, nil -} - -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { - buf: [DARWIN_MAXPATHLEN]byte - _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return - return strings.clone_from_cstring(cstring(&buf[0])) -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - return strings.clone(string(path_ptr), allocator) -} - -access :: proc(path: string, mask: int) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _unix_access(cstr, c.int(mask)) == 0 -} - -flush :: proc(fd: Handle) -> Error { - return cast(Platform_Error)_unix_fsync(fd) -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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 :: proc(key, value: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - key_cstring := strings.clone_to_cstring(key, context.temp_allocator) - value_cstring := strings.clone_to_cstring(value, context.temp_allocator) - res := _unix_setenv(key_cstring, value_cstring, 1) - if res < 0 { - return get_last_error() - } - return nil -} - -unset_env :: proc(key: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - s := strings.clone_to_cstring(key, context.temp_allocator) - res := _unix_unsetenv(s) - if res < 0 { - return get_last_error() - } - return nil -} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - context.allocator = allocator - page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. - buf := make([dynamic]u8, page_size) - for { - cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) - if cwd != nil { - return string(cwd) - } - if get_last_error() != ERANGE { - delete(buf) - return "" - } - resize(&buf, len(buf)+page_size) - } - unreachable() -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_chdir(cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -make_directory :: proc(path: string, mode: u16 = 0o775) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_mkdir(path_cstr, mode) - if res == -1 { - return get_last_error() - } - return nil -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(i32(code)) -} - -@(require_results) -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) -} - -@(require_results) -dlopen :: proc(filename: string, flags: int) -> rawptr { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, c.int(flags)) - return handle -} -@(require_results) -dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { - assert(handle != nil) - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(symbol, context.temp_allocator) - proc_handle := _unix_dlsym(handle, cstr) - return proc_handle -} -dlclose :: proc(handle: rawptr) -> bool { - assert(handle != nil) - return _unix_dlclose(handle) == 0 -} -dlerror :: proc() -> string { - return string(_unix_dlerror()) -} - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - page_size = int(_unix_getpagesize()) - return page_size -} - -@(private, require_results) -_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 -} - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for _, i in res { - res[i] = string(runtime.args__[i]) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} - -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { - result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) - if result < 0 { - return 0, get_last_error() - } - return Socket(result), nil -} - -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := _unix_connect(c.int(sd), addr, len) - if result < 0 { - return get_last_error() - } - return nil -} - -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Error) { - result := _unix_bind(c.int(sd), addr, len) - if result < 0 { - return get_last_error() - } - return nil -} - -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { - result := _unix_accept(c.int(sd), rawptr(addr), len) - if result < 0 { - return 0, get_last_error() - } - return Socket(result), nil -} - -listen :: proc(sd: Socket, backlog: int) -> (Error) { - result := _unix_listen(c.int(sd), c.int(backlog)) - if result < 0 { - return get_last_error() - } - return nil -} - -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := _unix_setsockopt(c.int(sd), c.int(level), c.int(optname), optval, optlen) - if result < 0 { - return get_last_error() - } - return nil -} - -getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - optlen := optlen - result := _unix_getsockopt(c.int(sd), c.int(level), c.int(optname), optval, &optlen) - if result < 0 { - return get_last_error() - } - return nil -} - -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { - result := _unix_recvfrom(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addr_size) - if result < 0 { - return 0, get_last_error() - } - return u32(result), nil -} - -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_recv(c.int(sd), raw_data(data), len(data), c.int(flags)) - if result < 0 { - return 0, get_last_error() - } - return u32(result), nil -} - -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { - result := _unix_sendto(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addrlen) - if result < 0 { - return 0, get_last_error() - } - return u32(result), nil -} - -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags)) - if result < 0 { - return 0, get_last_error() - } - return u32(result), nil -} - -shutdown :: proc(sd: Socket, how: int) -> (Error) { - result := _unix_shutdown(c.int(sd), c.int(how)) - if result < 0 { - return get_last_error() - } - return nil -} - -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { - result := _unix__fcntl(Handle(fd), c.int(cmd), uintptr(arg)) - if result < 0 { - return 0, get_last_error() - } - return int(result), nil -} diff --git a/core/os/os_essence.odin b/core/os/os_essence.odin deleted file mode 100644 index 75c4c1156..000000000 --- a/core/os/os_essence.odin +++ /dev/null @@ -1,60 +0,0 @@ -package os - -import "core:sys/es" - -Handle :: distinct int -_Platform_Error :: enum i32 {NONE} - -// ERROR_NONE :: Error(es.SUCCESS) - -O_RDONLY :: 0x1 -O_WRONLY :: 0x2 -O_CREATE :: 0x4 -O_TRUNC :: 0x8 - -stderr : Handle = 0 - -current_thread_id :: proc "contextless" () -> int { - return (int) (es.ThreadGetID(es.CURRENT_THREAD)) -} - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - return es.HeapAllocate(size, zero_memory) -} - -heap_free :: proc(ptr: rawptr) { - es.HeapFree(ptr) -} - -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - return es.HeapReallocate(ptr, new_size, false) -} - -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { - return (Handle) (0), (Error) (1) -} - -close :: proc(fd: Handle) -> Error { - return (Error) (1) -} - -file_size :: proc(fd: Handle) -> (i64, Error) { - return (i64) (0), (Error) (1) -} - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - return (int) (0), (Error) (1) -} - -write :: proc(fd: Handle, data: []u8) -> (int, Error) { - return (int) (0), (Error) (1) -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - return (i64) (0), (Error) (1) -} - -flush :: proc(fd: Handle) -> Error { - // do nothing - return nil -} \ No newline at end of file diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin deleted file mode 100644 index 82b5a2f0f..000000000 --- a/core/os/os_freebsd.odin +++ /dev/null @@ -1,982 +0,0 @@ -package os - -foreign import dl "system:dl" -foreign import libc "system:c" - -import "base:runtime" -import "core:strings" -import "core:c" -import "core:sys/freebsd" - -Handle :: distinct i32 -File_Time :: distinct u64 - -INVALID_HANDLE :: ~Handle(0) - -_Platform_Error :: enum i32 { - NONE = 0, - EPERM = 1, - ENOENT = 2, - ESRCH = 3, - EINTR = 4, - EIO = 5, - ENXIO = 6, - E2BIG = 7, - ENOEXEC = 8, - EBADF = 9, - ECHILD = 10, - EBEADLK = 11, - ENOMEM = 12, - EACCESS = 13, - EFAULT = 14, - ENOTBLK = 15, - EBUSY = 16, - EEXIST = 17, - EXDEV = 18, - ENODEV = 19, - ENOTDIR = 20, - EISDIR = 21, - EINVAL = 22, - ENFILE = 23, - EMFILE = 24, - ENOTTY = 25, - ETXTBSY = 26, - EFBIG = 27, - ENOSPC = 28, - ESPIPE = 29, - EROFS = 30, - EMLINK = 31, - EPIPE = 32, - EDOM = 33, - ERANGE = 34, /* Result too large */ - EAGAIN = 35, - EINPROGRESS = 36, - EALREADY = 37, - ENOTSOCK = 38, - EDESTADDRREQ = 39, - EMSGSIZE = 40, - EPROTOTYPE = 41, - ENOPROTOOPT = 42, - EPROTONOSUPPORT = 43, - ESOCKTNOSUPPORT = 44, - EOPNOTSUPP = 45, - EPFNOSUPPORT = 46, - EAFNOSUPPORT = 47, - EADDRINUSE = 48, - EADDRNOTAVAIL = 49, - ENETDOWN = 50, - ENETUNREACH = 51, - ENETRESET = 52, - ECONNABORTED = 53, - ECONNRESET = 54, - ENOBUFS = 55, - EISCONN = 56, - ENOTCONN = 57, - ESHUTDOWN = 58, - ETIMEDOUT = 60, - ECONNREFUSED = 61, - ELOOP = 62, - ENAMETOOLING = 63, - EHOSTDOWN = 64, - EHOSTUNREACH = 65, - ENOTEMPTY = 66, - EPROCLIM = 67, - EUSERS = 68, - EDQUOT = 69, - ESTALE = 70, - EBADRPC = 72, - ERPCMISMATCH = 73, - EPROGUNAVAIL = 74, - EPROGMISMATCH = 75, - EPROCUNAVAIL = 76, - ENOLCK = 77, - ENOSYS = 78, - EFTYPE = 79, - EAUTH = 80, - ENEEDAUTH = 81, - EIDRM = 82, - ENOMSG = 83, - EOVERFLOW = 84, - ECANCELED = 85, - EILSEQ = 86, - ENOATTR = 87, - EDOOFUS = 88, - EBADMSG = 89, - EMULTIHOP = 90, - ENOLINK = 91, - EPROTO = 92, - ENOTCAPABLE = 93, - ECAPMODE = 94, - ENOTRECOVERABLE = 95, - EOWNERDEAD = 96, -} -EPERM :: Platform_Error.EPERM -ENOENT :: Platform_Error.ENOENT -ESRCH :: Platform_Error.ESRCH -EINTR :: Platform_Error.EINTR -EIO :: Platform_Error.EIO -ENXIO :: Platform_Error.ENXIO -E2BIG :: Platform_Error.E2BIG -ENOEXEC :: Platform_Error.ENOEXEC -EBADF :: Platform_Error.EBADF -ECHILD :: Platform_Error.ECHILD -EBEADLK :: Platform_Error.EBEADLK -ENOMEM :: Platform_Error.ENOMEM -EACCESS :: Platform_Error.EACCESS -EFAULT :: Platform_Error.EFAULT -ENOTBLK :: Platform_Error.ENOTBLK -EBUSY :: Platform_Error.EBUSY -EEXIST :: Platform_Error.EEXIST -EXDEV :: Platform_Error.EXDEV -ENODEV :: Platform_Error.ENODEV -ENOTDIR :: Platform_Error.ENOTDIR -EISDIR :: Platform_Error.EISDIR -EINVAL :: Platform_Error.EINVAL -ENFILE :: Platform_Error.ENFILE -EMFILE :: Platform_Error.EMFILE -ENOTTY :: Platform_Error.ENOTTY -ETXTBSY :: Platform_Error.ETXTBSY -EFBIG :: Platform_Error.EFBIG -ENOSPC :: Platform_Error.ENOSPC -ESPIPE :: Platform_Error.ESPIPE -EROFS :: Platform_Error.EROFS -EMLINK :: Platform_Error.EMLINK -EPIPE :: Platform_Error.EPIPE -EDOM :: Platform_Error.EDOM -ERANGE :: Platform_Error.ERANGE -EAGAIN :: Platform_Error.EAGAIN -EINPROGRESS :: Platform_Error.EINPROGRESS -EALREADY :: Platform_Error.EALREADY -ENOTSOCK :: Platform_Error.ENOTSOCK -EDESTADDRREQ :: Platform_Error.EDESTADDRREQ -EMSGSIZE :: Platform_Error.EMSGSIZE -EPROTOTYPE :: Platform_Error.EPROTOTYPE -ENOPROTOOPT :: Platform_Error.ENOPROTOOPT -EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT -ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT -EOPNOTSUPP :: Platform_Error.EOPNOTSUPP -EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT -EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT -EADDRINUSE :: Platform_Error.EADDRINUSE -EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL -ENETDOWN :: Platform_Error.ENETDOWN -ENETUNREACH :: Platform_Error.ENETUNREACH -ENETRESET :: Platform_Error.ENETRESET -ECONNABORTED :: Platform_Error.ECONNABORTED -ECONNRESET :: Platform_Error.ECONNRESET -ENOBUFS :: Platform_Error.ENOBUFS -EISCONN :: Platform_Error.EISCONN -ENOTCONN :: Platform_Error.ENOTCONN -ESHUTDOWN :: Platform_Error.ESHUTDOWN -ETIMEDOUT :: Platform_Error.ETIMEDOUT -ECONNREFUSED :: Platform_Error.ECONNREFUSED -ELOOP :: Platform_Error.ELOOP -ENAMETOOLING :: Platform_Error.ENAMETOOLING -EHOSTDOWN :: Platform_Error.EHOSTDOWN -EHOSTUNREACH :: Platform_Error.EHOSTUNREACH -ENOTEMPTY :: Platform_Error.ENOTEMPTY -EPROCLIM :: Platform_Error.EPROCLIM -EUSERS :: Platform_Error.EUSERS -EDQUOT :: Platform_Error.EDQUOT -ESTALE :: Platform_Error.ESTALE -EBADRPC :: Platform_Error.EBADRPC -ERPCMISMATCH :: Platform_Error.ERPCMISMATCH -EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL -EPROGMISMATCH :: Platform_Error.EPROGMISMATCH -EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL -ENOLCK :: Platform_Error.ENOLCK -ENOSYS :: Platform_Error.ENOSYS -EFTYPE :: Platform_Error.EFTYPE -EAUTH :: Platform_Error.EAUTH -ENEEDAUTH :: Platform_Error.ENEEDAUTH -EIDRM :: Platform_Error.EIDRM -ENOMSG :: Platform_Error.ENOMSG -EOVERFLOW :: Platform_Error.EOVERFLOW -ECANCELED :: Platform_Error.ECANCELED -EILSEQ :: Platform_Error.EILSEQ -ENOATTR :: Platform_Error.ENOATTR -EDOOFUS :: Platform_Error.EDOOFUS -EBADMSG :: Platform_Error.EBADMSG -EMULTIHOP :: Platform_Error.EMULTIHOP -ENOLINK :: Platform_Error.ENOLINK -EPROTO :: Platform_Error.EPROTO -ENOTCAPABLE :: Platform_Error.ENOTCAPABLE -ECAPMODE :: Platform_Error.ECAPMODE -ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE -EOWNERDEAD :: Platform_Error.EOWNERDEAD - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_NONBLOCK :: 0x00004 -O_APPEND :: 0x00008 -O_ASYNC :: 0x00040 -O_SYNC :: 0x00080 -O_CREATE :: 0x00200 -O_TRUNC :: 0x00400 -O_EXCL :: 0x00800 -O_NOCTTY :: 0x08000 -O_CLOEXEC :: 0100000 - - -SEEK_DATA :: 3 -SEEK_HOLE :: 4 -SEEK_MAX :: SEEK_HOLE - -// NOTE: These are OS specific! -// Do not mix these up! -RTLD_LAZY :: 0x001 -RTLD_NOW :: 0x002 -//RTLD_BINDING_MASK :: 0x3 // Called MODEMASK in dlfcn.h -RTLD_GLOBAL :: 0x100 -RTLD_LOCAL :: 0x000 -RTLD_TRACE :: 0x200 -RTLD_NODELETE :: 0x01000 -RTLD_NOLOAD :: 0x02000 - -MAX_PATH :: 1024 - -KINFO_FILE_SIZE :: 1392 - -args := _alloc_command_line_arguments() - -Unix_File_Time :: struct { - seconds: time_t, - nanoseconds: c.long, -} - -dev_t :: u64 -ino_t :: u64 -nlink_t :: u64 -off_t :: i64 -mode_t :: u16 -pid_t :: u32 -uid_t :: u32 -gid_t :: u32 -blkcnt_t :: i64 -blksize_t :: i32 -fflags_t :: u32 - -when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ { - time_t :: i64 -} else { - time_t :: i32 -} - - -OS_Stat :: struct { - device_id: dev_t, - serial: ino_t, - nlink: nlink_t, - mode: mode_t, - _padding0: i16, - uid: uid_t, - gid: gid_t, - _padding1: i32, - rdev: dev_t, - - last_access: Unix_File_Time, - modified: Unix_File_Time, - status_change: Unix_File_Time, - birthtime: Unix_File_Time, - - size: off_t, - blocks: blkcnt_t, - block_size: blksize_t, - - flags: fflags_t, - gen: u64, - lspare: [10]u64, -} - -KInfo_File :: struct { - structsize: c.int, - type: c.int, - fd: c.int, - ref_count: c.int, - flags: c.int, - pad0: c.int, - offset: i64, - - // NOTE(Feoramund): This field represents a complicated union that I am - // avoiding implementing for now. I only need the path data below. - _union: [336]byte, - - path: [MAX_PATH]c.char, -} - -// since FreeBSD v12 -Dirent :: struct { - ino: ino_t, - off: off_t, - reclen: u16, - type: u8, - _pad0: u8, - namlen: u16, - _pad1: u16, - name: [256]byte, -} - -Dir :: distinct rawptr // DIR* - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket -//S_ISVTX :: 0o001000 // Save swapped text even after use - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - - // Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - - // Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - - -@(require_results) S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } -@(require_results) S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } -@(require_results) S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } -@(require_results) S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } -@(require_results) S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } -@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } -@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0 // Test for file existance -X_OK :: 1 // Test for execute permission -W_OK :: 2 // Test for write permission -R_OK :: 4 // Test for read permission - -F_KINFO :: 22 - -foreign libc { - @(link_name="__error") __Error_location :: proc() -> ^c.int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _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="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- -} -foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- - - @(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int --- -} - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - return Platform_Error(__Error_location()^) -} - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), u16(mode)) - if handle == -1 { - return INVALID_HANDLE, get_last_error() - } - return handle, nil -} - -close :: proc(fd: Handle) -> Error { - result := _unix_close(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> Error { - return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) -} - -// If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. -// In practice a read/write call would probably never read/write these big buffers all at once, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - to_read := min(c.size_t(len(data)), MAX_RW) - bytes_read := _unix_read(fd, &data[0], to_read) - if bytes_read == -1 { - return -1, get_last_error() - } - return int(bytes_read), nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - bytes_written := _unix_write(fd, &data[0], to_write) - if bytes_written == -1 { - return -1, get_last_error() - } - return int(bytes_written), nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) - - return bytes_read, cast(_Platform_Error)errno -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) - - return bytes_written, cast(_Platform_Error)errno -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - res := _unix_seek(fd, offset, c.int(whence)) - if res == -1 { - errno := get_last_error() - switch errno { - case .EINVAL: - return 0, .Invalid_Offset - case: - return 0, errno - } - } - return res, nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (size: i64, err: Error) { - size = -1 - s := _fstat(fd) or_return - size = s.size - return -} - -rename :: proc(old_path, new_path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - res := _unix_rename(old_path_cstr, new_path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -remove :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_unlink(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_mkdir(path_cstr, mode) - if res == -1 { - return get_last_error() - } - return nil -} - -remove_directory :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_rmdir(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -@(require_results) -is_file_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_file_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_dir_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -@(require_results) -is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -is_file :: proc {is_file_path, is_file_handle} -is_dir :: proc {is_dir_path, is_dir_handle} - -@(require_results) -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cpath := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_access(cpath, O_RDONLY) - return res == 0 -} - -// NOTE(bill): Uses startup to initialize it - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -/* TODO(zangent): Implement these! -last_write_time :: proc(fd: Handle) -> File_Time {} -last_write_time_by_name :: proc(name: string) -> File_Time {} -*/ -@(require_results) -last_write_time :: proc(fd: Handle) -> (File_Time, Error) { - s, err := _fstat(fd) - if err != nil { - return 0, err - } - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { - s, err := _stat(name) - if err != nil { - return 0, err - } - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(private, require_results, no_sanitize_memory) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - s: OS_Stat = --- - result := _unix_lstat(cstr, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_lstat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - s: OS_Stat = --- - result := _unix_fstat(fd, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - - if result == nil { - end_of_stream = true - return - } - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = MAX_PATH - buf := make([]byte, MAX_PATH) - for { - rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) - if rc == -1 { - delete(buf) - return "", get_last_error() - } else if rc == int(bufsz) { - bufsz += MAX_PATH - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } - - return "", Error{} -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup := _unix_dup(fd) - if dup == -1 { - return INVALID_HANDLE, get_last_error() - } - return dup, nil -} - -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { - // NOTE(Feoramund): The situation isn't ideal, but this was the best way I - // could find to implement this. There are a couple outstanding bug reports - // regarding the desire to retrieve an absolute path from a handle, but to - // my knowledge, there hasn't been any work done on it. - // - // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 - // - // This may be unreliable, according to a comment from 2023. - - kinfo: KInfo_File - kinfo.structsize = KINFO_FILE_SIZE - - res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo) - if res == -1 { - return "", get_last_error() - } - - path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) - return path, nil -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - return strings.clone(string(path_ptr), allocator) -} - -access :: proc(path: string, mask: int) -> (bool, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - cstr := strings.clone_to_cstring(path, context.temp_allocator) - result := _unix_access(cstr, c.int(mask)) - if result == -1 { - return false, get_last_error() - } - return true, nil -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - context.allocator = allocator - // NOTE(tetra): I would use PATH_MAX here, but I was not able to find - // an authoritative value for it across all systems. - // The largest value I could find was 4096, so might as well use the page size. - page_size := get_page_size() - buf := make([dynamic]u8, page_size) - #no_bounds_check for { - cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) - if cwd != nil { - return string(cwd) - } - if get_last_error() != ERANGE { - delete(buf) - return "" - } - resize(&buf, len(buf)+page_size) - } - unreachable() -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_chdir(cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(c.int(code)) -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return cast(int) pthread_getthreadid_np() -} - -@(require_results) -dlopen :: proc(filename: string, flags: int) -> rawptr { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, c.int(flags)) - return handle -} -@(require_results) -dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { - assert(handle != nil) - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(symbol, context.temp_allocator) - proc_handle := _unix_dlsym(handle, cstr) - return proc_handle -} -dlclose :: proc(handle: rawptr) -> bool { - assert(handle != nil) - return _unix_dlclose(handle) == 0 -} -dlerror :: proc() -> string { - return string(_unix_dlerror()) -} - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - page_size = int(_unix_getpagesize()) - return page_size -} - -@(private, require_results) -_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 -} - - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for _, i in res { - res[i] = string(runtime.args__[i]) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin deleted file mode 100644 index c22a6d7d5..000000000 --- a/core/os/os_freestanding.odin +++ /dev/null @@ -1,4 +0,0 @@ -#+build freestanding -package os - -#panic("package os does not support a freestanding target") diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin deleted file mode 100644 index ad984e33c..000000000 --- a/core/os/os_haiku.odin +++ /dev/null @@ -1,544 +0,0 @@ -package os - -foreign import lib "system:c" - -import "base:runtime" -import "core:c" -import "core:c/libc" -import "core:strings" -import "core:sys/haiku" -import "core:sys/posix" - -Handle :: i32 -Pid :: i32 -File_Time :: i64 -_Platform_Error :: haiku.Errno - -MAX_PATH :: haiku.PATH_MAX - -ENOSYS :: _Platform_Error(haiku.Errno.ENOSYS) - -INVALID_HANDLE :: ~Handle(0) - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -pid_t :: haiku.pid_t -off_t :: haiku.off_t -dev_t :: haiku.dev_t -ino_t :: haiku.ino_t -mode_t :: haiku.mode_t -nlink_t :: haiku.nlink_t -uid_t :: haiku.uid_t -gid_t :: haiku.gid_t -blksize_t :: haiku.blksize_t -blkcnt_t :: haiku.blkcnt_t -time_t :: haiku.time_t - - -Unix_File_Time :: struct { - seconds: time_t, - nanoseconds: c.long, -} - -OS_Stat :: struct { - device_id: dev_t, // device ID that this file resides on - serial: ino_t, // this file's serial inode ID - mode: mode_t, // file mode (rwx for user, group, etc) - nlink: nlink_t, // number of hard links to this file - uid: uid_t, // user ID of the file's owner - gid: gid_t, // group ID of the file's group - size: off_t, // file size, in bytes - rdev: dev_t, // device type (not used) - block_size: blksize_t, // optimal blocksize for I/O - - last_access: Unix_File_Time, // time of last access - modified: Unix_File_Time, // time of last data modification - status_change: Unix_File_Time, // time of last file status change - birthtime: Unix_File_Time, // time of file creation - - type: u32, // attribute/index type - - blocks: blkcnt_t, // blocks allocated for file -} - -/* file access modes for open() */ -O_RDONLY :: 0x0000 /* read only */ -O_WRONLY :: 0x0001 /* write only */ -O_RDWR :: 0x0002 /* read and write */ -O_ACCMODE :: 0x0003 /* mask to get the access modes above */ -O_RWMASK :: O_ACCMODE - -/* flags for open() */ -O_EXCL :: 0x0100 /* exclusive creat */ -O_CREATE :: 0x0200 /* create and open file */ -O_TRUNC :: 0x0400 /* open with truncation */ -O_NOCTTY :: 0x1000 /* don't make tty the controlling tty */ -O_NOTRAVERSE :: 0x2000 /* do not traverse leaf link */ - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket -S_ISVTX :: 0o001000 // Save swapped text even after use - -// File mode - // Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - - // Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - - // Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISTXT :: 0o1000 // Sticky bit - -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } - -__error :: libc.errno -_unix_open :: posix.open - -foreign lib { - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- - - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- -} - -MAXNAMLEN :: haiku.NAME_MAX - -Dirent :: struct { - dev: dev_t, - pdef: dev_t, - ino: ino_t, - pino: ino_t, - reclen: u16, - name: [MAXNAMLEN + 1]byte, // name -} - -Dir :: distinct rawptr // DIR* - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - return Platform_Error(__error()^) -} - -@(require_results) -fork :: proc() -> (Pid, Error) { - pid := _unix_fork() - if pid == -1 { - return Pid(-1), get_last_error() - } - return Pid(pid), nil -} - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := cast(Handle)_unix_open(cstr, transmute(posix.O_Flags)i32(flags), transmute(posix.mode_t)i32(mode)) - if handle == -1 { - return INVALID_HANDLE, get_last_error() - } - return handle, nil -} - -close :: proc(fd: Handle) -> Error { - result := _unix_close(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> Error { - result := _unix_fsync(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -// In practice a read/write call would probably never read/write these big buffers all at once, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - to_read := min(c.size_t(len(data)), MAX_RW) - bytes_read := _unix_read(fd, &data[0], to_read) - if bytes_read == -1 { - return -1, get_last_error() - } - return int(bytes_read), nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - bytes_written := _unix_write(fd, &data[0], to_write) - if bytes_written == -1 { - return -1, get_last_error() - } - return int(bytes_written), nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) - if bytes_read < 0 { - return -1, get_last_error() - } - return bytes_read, nil -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) - if bytes_written < 0 { - return -1, get_last_error() - } - return bytes_written, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - res := _unix_seek(fd, offset, c.int(whence)) - if res == -1 { - errno := get_last_error() - switch errno { - case .BAD_VALUE: - return 0, .Invalid_Offset - } - return 0, errno - } - return res, nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (i64, Error) { - s, err := _fstat(fd) - if err != nil { - return -1, err - } - return s.size, nil -} - -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for arg, i in runtime.args__ { - res[i] = string(arg) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} - -@(private, require_results, no_sanitize_memory) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_stat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_lstat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_fstat(fd, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - - if result == nil { - end_of_stream = true - return - } - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = MAX_PATH - buf := make([]byte, MAX_PATH) - for { - rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) - if rc == -1 { - delete(buf) - return "", get_last_error() - } else if rc == int(bufsz) { - bufsz += MAX_PATH - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } -} - -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { - return "", Error(ENOSYS) -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - path_cstr := cstring(path_ptr) - return strings.clone(string(path_cstr), allocator) -} - -access :: proc(path: string, mask: int) -> (bool, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_access(cstr, c.int(mask)) - if res == -1 { - return false, get_last_error() - } - return true, nil -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} - - -@(private, require_results) -_processor_core_count :: proc() -> int { - info: haiku.system_info - haiku.get_system_info(&info) - return int(info.cpu_count) -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(i32(code)) -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return int(haiku.find_thread(nil)) -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup := _unix_dup(fd) - if dup == -1 { - return INVALID_HANDLE, get_last_error() - } - return dup, nil -} diff --git a/core/os/os_js.odin b/core/os/os_js.odin deleted file mode 100644 index 1870218d3..000000000 --- a/core/os/os_js.odin +++ /dev/null @@ -1,275 +0,0 @@ -#+build js -package os - -foreign import "odin_env" - -@(require_results) -is_path_separator :: proc(c: byte) -> bool { - return c == '/' || c == '\\' -} - -Handle :: distinct u32 - -stdout: Handle = 1 -stderr: Handle = 2 - -@(require_results) -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -close :: proc(fd: Handle) -> Error { - return nil -} - -flush :: proc(fd: Handle) -> (err: Error) { - return nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - foreign odin_env { - @(link_name="write") - _write :: proc "contextless" (fd: Handle, p: []byte) --- - } - _write(fd, data) - return len(data), nil -} - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -file_size :: proc(fd: Handle) -> (i64, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - unimplemented("core:os procedure not supported on JS target") -} -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -exists :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -is_file :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -is_dir :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - unimplemented("core:os procedure not supported on JS target") -} - -set_current_directory :: proc(path: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - - -change_directory :: proc(path: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - -remove_directory :: proc(path: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - -link :: proc(old_name, new_name: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -unlink :: proc(path: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - - -rename :: proc(old_path, new_path: string) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - -ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -truncate :: proc(path: string, length: i64) -> (err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - - -remove :: proc(name: string) -> Error { - unimplemented("core:os procedure not supported on JS target") -} - - -@(require_results) -pipe :: proc() -> (r, w: Handle, err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { - unimplemented("core:os procedure not supported on JS target") -} - -File_Time :: distinct u64 - -_Platform_Error :: enum i32 { - NONE = 0, - FILE_NOT_FOUND = 2, - PATH_NOT_FOUND = 3, - ACCESS_DENIED = 5, - INVALID_HANDLE = 6, - NOT_ENOUGH_MEMORY = 8, - NO_MORE_FILES = 18, - HANDLE_EOF = 38, - NETNAME_DELETED = 64, - FILE_EXISTS = 80, - INVALID_PARAMETER = 87, - BROKEN_PIPE = 109, - BUFFER_OVERFLOW = 111, - INSUFFICIENT_BUFFER = 122, - MOD_NOT_FOUND = 126, - PROC_NOT_FOUND = 127, - DIR_NOT_EMPTY = 145, - ALREADY_EXISTS = 183, - ENVVAR_NOT_FOUND = 203, - MORE_DATA = 234, - OPERATION_ABORTED = 995, - IO_PENDING = 997, - NOT_FOUND = 1168, - PRIVILEGE_NOT_HELD = 1314, - WSAEACCES = 10013, - WSAECONNRESET = 10054, - - // Windows reserves errors >= 1<<29 for application use - FILE_IS_PIPE = 1<<29 + 0, - FILE_IS_NOT_DIR = 1<<29 + 1, - NEGATIVE_OFFSET = 1<<29 + 2, -} - - -INVALID_HANDLE :: ~Handle(0) - - - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 - - -ERROR_FILE_NOT_FOUND :: Platform_Error.FILE_NOT_FOUND -ERROR_PATH_NOT_FOUND :: Platform_Error.PATH_NOT_FOUND -ERROR_ACCESS_DENIED :: Platform_Error.ACCESS_DENIED -ERROR_INVALID_HANDLE :: Platform_Error.INVALID_HANDLE -ERROR_NOT_ENOUGH_MEMORY :: Platform_Error.NOT_ENOUGH_MEMORY -ERROR_NO_MORE_FILES :: Platform_Error.NO_MORE_FILES -ERROR_HANDLE_EOF :: Platform_Error.HANDLE_EOF -ERROR_NETNAME_DELETED :: Platform_Error.NETNAME_DELETED -ERROR_FILE_EXISTS :: Platform_Error.FILE_EXISTS -ERROR_INVALID_PARAMETER :: Platform_Error.INVALID_PARAMETER -ERROR_BROKEN_PIPE :: Platform_Error.BROKEN_PIPE -ERROR_BUFFER_OVERFLOW :: Platform_Error.BUFFER_OVERFLOW -ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER -ERROR_MOD_NOT_FOUND :: Platform_Error.MOD_NOT_FOUND -ERROR_PROC_NOT_FOUND :: Platform_Error.PROC_NOT_FOUND -ERROR_DIR_NOT_EMPTY :: Platform_Error.DIR_NOT_EMPTY -ERROR_ALREADY_EXISTS :: Platform_Error.ALREADY_EXISTS -ERROR_ENVVAR_NOT_FOUND :: Platform_Error.ENVVAR_NOT_FOUND -ERROR_MORE_DATA :: Platform_Error.MORE_DATA -ERROR_OPERATION_ABORTED :: Platform_Error.OPERATION_ABORTED -ERROR_IO_PENDING :: Platform_Error.IO_PENDING -ERROR_NOT_FOUND :: Platform_Error.NOT_FOUND -ERROR_PRIVILEGE_NOT_HELD :: Platform_Error.PRIVILEGE_NOT_HELD -WSAEACCES :: Platform_Error.WSAEACCES -WSAECONNRESET :: Platform_Error.WSAECONNRESET - -ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe -ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir - -args: []string - -@(require_results) -last_write_time :: proc(fd: Handle) -> (File_Time, Error) { - unimplemented("core:os procedure not supported on JS target") -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { - unimplemented("core:os procedure not supported on JS target") -} - - -@(require_results) -get_page_size :: proc() -> int { - unimplemented("core:os procedure not supported on JS target") -} - -@(private, require_results) -_processor_core_count :: proc() -> int { - return 1 -} - -exit :: proc "contextless" (code: int) -> ! { - unimplemented_contextless("core:os procedure not supported on JS target") -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return 0 -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return "", false -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - return "", .Env_Var_Not_Found -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} \ No newline at end of file diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin deleted file mode 100644 index 4c32676c6..000000000 --- a/core/os/os_linux.odin +++ /dev/null @@ -1,1233 +0,0 @@ -package os - -foreign import dl "system:dl" -foreign import libc "system:c" - -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" - -Handle :: distinct i32 -Pid :: distinct i32 -File_Time :: distinct u64 -Socket :: distinct int - -INVALID_HANDLE :: ~Handle(0) - -_Platform_Error :: linux.Errno -EPERM :: Platform_Error.EPERM -ENOENT :: Platform_Error.ENOENT -ESRCH :: Platform_Error.ESRCH -EINTR :: Platform_Error.EINTR -EIO :: Platform_Error.EIO -ENXIO :: Platform_Error.ENXIO -EBADF :: Platform_Error.EBADF -EAGAIN :: Platform_Error.EAGAIN -ENOMEM :: Platform_Error.ENOMEM -EACCES :: Platform_Error.EACCES -EFAULT :: Platform_Error.EFAULT -EEXIST :: Platform_Error.EEXIST -ENODEV :: Platform_Error.ENODEV -ENOTDIR :: Platform_Error.ENOTDIR -EISDIR :: Platform_Error.EISDIR -EINVAL :: Platform_Error.EINVAL -ENFILE :: Platform_Error.ENFILE -EMFILE :: Platform_Error.EMFILE -ETXTBSY :: Platform_Error.ETXTBSY -EFBIG :: Platform_Error.EFBIG -ENOSPC :: Platform_Error.ENOSPC -ESPIPE :: Platform_Error.ESPIPE -EROFS :: Platform_Error.EROFS -EPIPE :: Platform_Error.EPIPE - -ERANGE :: Platform_Error.ERANGE /* Result too large */ -EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock would occur */ -ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ -ENOLCK :: Platform_Error.ENOLCK /* No record locks available */ - -ENOSYS :: Platform_Error.ENOSYS /* Invalid system call number */ - -ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ -ELOOP :: Platform_Error.ELOOP /* Too many symbolic links encountered */ -EWOULDBLOCK :: Platform_Error.EWOULDBLOCK /* Operation would block */ -ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ -EIDRM :: Platform_Error.EIDRM /* Identifier removed */ -ECHRNG :: Platform_Error.ECHRNG /* Channel number out of range */ -EL2NSYNC :: Platform_Error.EL2NSYNC /* Level 2 not synchronized */ -EL3HLT :: Platform_Error.EL3HLT /* Level 3 halted */ -EL3RST :: Platform_Error.EL3RST /* Level 3 reset */ -ELNRNG :: Platform_Error.ELNRNG /* Link number out of range */ -EUNATCH :: Platform_Error.EUNATCH /* Protocol driver not attached */ -ENOCSI :: Platform_Error.ENOCSI /* No CSI structure available */ -EL2HLT :: Platform_Error.EL2HLT /* Level 2 halted */ -EBADE :: Platform_Error.EBADE /* Invalid exchange */ -EBADR :: Platform_Error.EBADR /* Invalid request descriptor */ -EXFULL :: Platform_Error.EXFULL /* Exchange full */ -ENOANO :: Platform_Error.ENOANO /* No anode */ -EBADRQC :: Platform_Error.EBADRQC /* Invalid request code */ -EBADSLT :: Platform_Error.EBADSLT /* Invalid slot */ -EDEADLOCK :: Platform_Error.EDEADLOCK -EBFONT :: Platform_Error.EBFONT /* Bad font file format */ -ENOSTR :: Platform_Error.ENOSTR /* Device not a stream */ -ENODATA :: Platform_Error.ENODATA /* No data available */ -ETIME :: Platform_Error.ETIME /* Timer expired */ -ENOSR :: Platform_Error.ENOSR /* Out of streams resources */ -ENONET :: Platform_Error.ENONET /* Machine is not on the network */ -ENOPKG :: Platform_Error.ENOPKG /* Package not installed */ -EREMOTE :: Platform_Error.EREMOTE /* Object is remote */ -ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ -EADV :: Platform_Error.EADV /* Advertise error */ -ESRMNT :: Platform_Error.ESRMNT /* Srmount error */ -ECOMM :: Platform_Error.ECOMM /* Communication error on send */ -EPROTO :: Platform_Error.EPROTO /* Protocol error */ -EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ -EDOTDOT :: Platform_Error.EDOTDOT /* RFS specific error */ -EBADMSG :: Platform_Error.EBADMSG /* Not a data message */ -EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large for defined data type */ -ENOTUNIQ :: Platform_Error.ENOTUNIQ /* Name not unique on network */ -EBADFD :: Platform_Error.EBADFD /* File descriptor in bad state */ -EREMCHG :: Platform_Error.EREMCHG /* Remote address changed */ -ELIBACC :: Platform_Error.ELIBACC /* Can not access a needed shared library */ -ELIBBAD :: Platform_Error.ELIBBAD /* Accessing a corrupted shared library */ -ELIBSCN :: Platform_Error.ELIBSCN /* .lib section in a.out corrupted */ -ELIBMAX :: Platform_Error.ELIBMAX /* Attempting to link in too many shared libraries */ -ELIBEXEC :: Platform_Error.ELIBEXEC /* Cannot exec a shared library directly */ -EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ -ERESTART :: Platform_Error.ERESTART /* Interrupted system call should be restarted */ -ESTRPIPE :: Platform_Error.ESTRPIPE /* Streams pipe error */ -EUSERS :: Platform_Error.EUSERS /* Too many users */ -ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ -EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ -EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ -EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ -ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol not available */ -EPROTONOSUPPOR :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ -ESOCKTNOSUPPOR :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ -EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported on transport endpoint */ -EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ -EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol */ -EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ -EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Cannot assign requested address */ -ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ -ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ -ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection because of reset */ -ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ -ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ -ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ -EISCONN :: Platform_Error.EISCONN /* Transport endpoint is already connected */ -ENOTCONN :: Platform_Error.ENOTCONN /* Transport endpoint is not connected */ -ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Cannot send after transport endpoint shutdown */ -ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: cannot splice */ -ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Connection timed out */ -ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ -EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ -EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ -EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ -EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ -ESTALE :: Platform_Error.ESTALE /* Stale file handle */ -EUCLEAN :: Platform_Error.EUCLEAN /* Structure needs cleaning */ -ENOTNAM :: Platform_Error.ENOTNAM /* Not a XENIX named type file */ -ENAVAIL :: Platform_Error.ENAVAIL /* No XENIX semaphores available */ -EISNAM :: Platform_Error.EISNAM /* Is a named type file */ -EREMOTEIO :: Platform_Error.EREMOTEIO /* Remote I/O error */ -EDQUOT :: Platform_Error.EDQUOT /* Quota exceeded */ - -ENOMEDIUM :: Platform_Error.ENOMEDIUM /* No medium found */ -EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE /* Wrong medium type */ -ECANCELED :: Platform_Error.ECANCELED /* Operation Canceled */ -ENOKEY :: Platform_Error.ENOKEY /* Required key not available */ -EKEYEXPIRED :: Platform_Error.EKEYEXPIRED /* Key has expired */ -EKEYREVOKED :: Platform_Error.EKEYREVOKED /* Key has been revoked */ -EKEYREJECTED :: Platform_Error.EKEYREJECTED /* Key was rejected by service */ - -/* for robust mutexes */ -EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Owner died */ -ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ - -ERFKILL :: Platform_Error.ERFKILL /* Operation not possible due to RF-kill */ - -EHWPOISON :: Platform_Error.EHWPOISON /* Memory page has hardware error */ - -ADDR_NO_RANDOMIZE :: 0x40000 - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 - - -SEEK_DATA :: 3 -SEEK_HOLE :: 4 -SEEK_MAX :: SEEK_HOLE - - -AF_UNSPEC: int : 0 -AF_UNIX: int : 1 -AF_LOCAL: int : AF_UNIX -AF_INET: int : 2 -AF_INET6: int : 10 -AF_PACKET: int : 17 -AF_BLUETOOTH: int : 31 - -SOCK_STREAM: int : 1 -SOCK_DGRAM: int : 2 -SOCK_RAW: int : 3 -SOCK_RDM: int : 4 -SOCK_SEQPACKET: int : 5 -SOCK_PACKET: int : 10 - -INADDR_ANY: c.ulong : 0 -INADDR_BROADCAST: c.ulong : 0xffffffff -INADDR_NONE: c.ulong : 0xffffffff -INADDR_DUMMY: c.ulong : 0xc0000008 - -IPPROTO_IP: int : 0 -IPPROTO_ICMP: int : 1 -IPPROTO_TCP: int : 6 -IPPROTO_UDP: int : 17 -IPPROTO_IPV6: int : 41 -IPPROTO_ETHERNET: int : 143 -IPPROTO_RAW: int : 255 - -SHUT_RD: int : 0 -SHUT_WR: int : 1 -SHUT_RDWR: int : 2 - - -SOL_SOCKET: int : 1 -SO_DEBUG: int : 1 -SO_REUSEADDR: int : 2 -SO_DONTROUTE: int : 5 -SO_BROADCAST: int : 6 -SO_SNDBUF: int : 7 -SO_RCVBUF: int : 8 -SO_KEEPALIVE: int : 9 -SO_OOBINLINE: int : 10 -SO_LINGER: int : 13 -SO_REUSEPORT: int : 15 -SO_RCVTIMEO_NEW: int : 66 -SO_SNDTIMEO_NEW: int : 67 - -TCP_NODELAY: int : 1 -TCP_CORK: int : 3 - -MSG_TRUNC : int : 0x20 - -// TODO: add remaining fcntl commands -// reference: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h -F_GETFL: int : 3 /* Get file flags */ -F_SETFL: int : 4 /* Set file flags */ - -// NOTE(zangent): These are OS specific! -// Do not mix these up! -RTLD_LAZY :: 0x0001 -RTLD_NOW :: 0x0002 -RTLD_BINDING_MASK :: 0x0003 -RTLD_GLOBAL :: 0x0100 -RTLD_NOLOAD :: 0x0004 -RTLD_DEEPBIND :: 0x0008 -RTLD_NODELETE :: 0x1000 - -socklen_t :: c.int - -Timeval :: struct { - seconds: i64, - microseconds: int, -} - -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - -Unix_File_Time :: struct { - seconds: i64, - nanoseconds: i64, -} - -when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { - OS_Stat :: struct { - device_id: u64, // ID of device containing file - serial: u64, // File serial number - mode: u32, // Mode of the file - nlink: u32, // Number of hard links - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - rdev: u64, // Device ID, if device - _: u64, // Padding - size: i64, // Size of the file, in bytes - block_size: i32, // Optimal blocksize for I/O - _: i32, // Padding - blocks: i64, // Number of 512-byte blocks allocated - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - - _reserved: [2]i32, - } - #assert(size_of(OS_Stat) == 128) -} else { - OS_Stat :: struct { - device_id: u64, // ID of device containing file - serial: u64, // File serial number - nlink: u64, // Number of hard links - mode: u32, // Mode of the file - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - _: i32, // 32 bits of padding - rdev: u64, // Device ID, if device - size: i64, // Size of the file, in bytes - block_size: i64, // Optimal bllocksize for I/O - blocks: i64, // Number of 512-byte blocks allocated - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - - _reserved: [3]i64, - } -} - -// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above -Dirent :: struct { - ino: u64, - off: u64, - reclen: u16, - type: u8, - name: [256]byte, -} - -ADDRESS_FAMILY :: u16 -SOCKADDR :: struct #packed { - sa_family: ADDRESS_FAMILY, - sa_data: [14]c.char, -} - -SOCKADDR_STORAGE_LH :: struct #packed { - ss_family: ADDRESS_FAMILY, - __ss_pad1: [6]c.char, - __ss_align: i64, - __ss_pad2: [112]c.char, -} - -sockaddr_in :: struct #packed { - sin_family: ADDRESS_FAMILY, - sin_port: u16be, - sin_addr: in_addr, - sin_zero: [8]c.char, -} - -sockaddr_in6 :: struct #packed { - sin6_family: ADDRESS_FAMILY, - sin6_port: u16be, - sin6_flowinfo: c.ulong, - sin6_addr: in6_addr, - sin6_scope_id: c.ulong, -} - -in_addr :: struct #packed { - s_addr: u32, -} - -in6_addr :: struct #packed { - s6_addr: [16]u8, -} - -rtnl_link_stats :: struct #packed { - rx_packets: u32, - tx_packets: u32, - rx_bytes: u32, - tx_bytes: u32, - rx_errors: u32, - tx_errors: u32, - rx_dropped: u32, - tx_dropped: u32, - multicast: u32, - collisions: u32, - rx_length_errors: u32, - rx_over_errors: u32, - rx_crc_errors: u32, - rx_frame_errors: u32, - rx_fifo_errors: u32, - rx_missed_errors: u32, - tx_aborted_errors: u32, - tx_carrier_errors: u32, - tx_fifo_errors: u32, - tx_heartbeat_errors: u32, - tx_window_errors: u32, - rx_compressed: u32, - tx_compressed: u32, - rx_nohandler: u32, -} - -SIOCGIFFLAG :: enum c.int { - UP = 0, /* Interface is up. */ - BROADCAST = 1, /* Broadcast address valid. */ - DEBUG = 2, /* Turn on debugging. */ - LOOPBACK = 3, /* Is a loopback net. */ - POINT_TO_POINT = 4, /* Interface is point-to-point link. */ - NO_TRAILERS = 5, /* Avoid use of trailers. */ - RUNNING = 6, /* Resources allocated. */ - NOARP = 7, /* No address resolution protocol. */ - PROMISC = 8, /* Receive all packets. */ - ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */ - MASTER = 10, /* Master of a load balancer. */ - SLAVE = 11, /* Slave of a load balancer. */ - MULTICAST = 12, /* Supports multicast. */ - PORTSEL = 13, /* Can set media type. */ - AUTOMEDIA = 14, /* Auto media select active. */ - DYNAMIC = 15, /* Dialup device with changing addresses. */ - LOWER_UP = 16, - DORMANT = 17, - ECHO = 18, -} -SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] - -ifaddrs :: struct { - next: ^ifaddrs, - name: cstring, - flags: SIOCGIFFLAGS, - address: ^SOCKADDR, - netmask: ^SOCKADDR, - broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address - data: rawptr, // Address-specific data. -} - -Dir :: distinct rawptr // DIR* - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - -// Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - -// Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - - -@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0 // Test for file existance -X_OK :: 1 // Test for execute permission -W_OK :: 2 // Test for write permission -R_OK :: 4 // Test for read permission - -AT_FDCWD :: ~uintptr(99) /* -100 */ -AT_REMOVEDIR :: uintptr(0x200) -AT_SYMLINK_NOFOLLOW :: uintptr(0x100) - -pollfd :: struct { - fd: c.int, - events: c.short, - revents: c.short, -} - -sigset_t :: distinct u64 - -foreign libc { - @(link_name="__errno_location") __errno_location :: proc() -> ^c.int --- - - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="get_nprocs") _unix_get_nprocs :: proc() -> c.int --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> c.int --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- - @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- -} -foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- - - @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- - @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- -} - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -// determine errno from syscall return value -@(private, require_results) -_get_errno :: proc(res: int) -> Error { - if res < 0 && res > -4096 { - return Platform_Error(-res) - } - return nil -} - -// get errno from libc -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - err := Platform_Error(__errno_location()^) - #partial switch err { - case .NONE: - return nil - case .EPERM: - return .Permission_Denied - case .EEXIST: - return .Exist - case .ENOENT: - return .Not_Exist - } - return err -} - -personality :: proc(persona: u64) -> Error { - res := unix.sys_personality(persona) - if res == -1 { - return _get_errno(res) - } - return nil -} - -@(require_results) -fork :: proc() -> (Pid, Error) { - pid := unix.sys_fork() - if pid == -1 { - return -1, _get_errno(pid) - } - return Pid(pid), nil -} - -execvp :: proc(path: string, args: []string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - args_cstrs := make([]cstring, len(args) + 2, context.temp_allocator) - args_cstrs[0] = strings.clone_to_cstring(path, context.temp_allocator) - for i := 0; i < len(args); i += 1 { - args_cstrs[i+1] = strings.clone_to_cstring(args[i], context.temp_allocator) - } - - _unix_execvp(path_cstr, raw_data(args_cstrs)) - return get_last_error() -} - - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := unix.sys_open(cstr, flags, uint(mode)) - if handle < 0 { - return INVALID_HANDLE, _get_errno(handle) - } - return Handle(handle), nil -} - -close :: proc(fd: Handle) -> Error { - return _get_errno(unix.sys_close(int(fd))) -} - -flush :: proc(fd: Handle) -> Error { - return _get_errno(unix.sys_fsync(int(fd))) -} - -// If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). -// `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin. -// In practice a read/write call would probably never read/write these big buffers all at once, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read := unix.sys_read(int(fd), raw_data(data), to_read) - if bytes_read < 0 { - return -1, _get_errno(bytes_read) - } - return bytes_read, nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written := unix.sys_write(int(fd), raw_data(data), to_write) - if bytes_written < 0 { - return -1, _get_errno(bytes_written) - } - return bytes_written, nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read := unix.sys_pread(int(fd), raw_data(data), to_read, offset) - if bytes_read < 0 { - return -1, _get_errno(bytes_read) - } - return bytes_read, nil -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written := unix.sys_pwrite(int(fd), raw_data(data), to_write, offset) - if bytes_written < 0 { - return -1, _get_errno(bytes_written) - } - return bytes_written, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - res := unix.sys_lseek(int(fd), offset, whence) - if res < 0 { - errno := _get_errno(int(res)) - switch errno { - case .EINVAL: - return 0, .Invalid_Offset - } - return 0, errno - } - return i64(res), nil -} - -@(require_results, no_sanitize_memory) -file_size :: proc(fd: Handle) -> (i64, Error) { - // deliberately uninitialized; the syscall fills this buffer for us - s: OS_Stat = --- - result := unix.sys_fstat(int(fd), rawptr(&s)) - if result < 0 { - return 0, _get_errno(result) - } - return max(s.size, 0), nil -} - -rename :: proc(old_path, new_path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr)) -} - -remove :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _get_errno(unix.sys_unlink(path_cstr)) -} - -make_directory :: proc(path: string, mode: u32 = 0o775) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _get_errno(unix.sys_mkdir(path_cstr, uint(mode))) -} - -remove_directory :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _get_errno(unix.sys_rmdir(path_cstr)) -} - -@(require_results) -is_file_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_file_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISREG(s.mode) -} - - -@(require_results) -is_dir_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -@(require_results) -is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -is_file :: proc {is_file_path, is_file_handle} -is_dir :: proc {is_dir_path, is_dir_handle} - -@(require_results) -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cpath := strings.clone_to_cstring(path, context.temp_allocator) - res := unix.sys_access(cpath, O_RDONLY) - return res == 0 -} - -// NOTE(bill): Uses startup to initialize it - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -/* TODO(zangent): Implement these! -last_write_time :: proc(fd: Handle) -> File_Time {} -last_write_time_by_name :: proc(name: string) -> File_Time {} -*/ -@(require_results) -last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { - s := _fstat(fd) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { - s := _stat(name) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(private, require_results, no_sanitize_memory) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized; the syscall fills this buffer for us - s: OS_Stat = --- - result := unix.sys_stat(cstr, &s) - if result < 0 { - return s, _get_errno(result) - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized; the syscall fills this buffer for us - s: OS_Stat = --- - result := unix.sys_lstat(cstr, &s) - if result < 0 { - return s, _get_errno(result) - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - // deliberately uninitialized; the syscall fills this buffer for us - s: OS_Stat = --- - result := unix.sys_fstat(int(fd), rawptr(&s)) - if result < 0 { - return s, _get_errno(result) - } - return s, nil -} - -@(private, require_results) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - err = nil - - if result == nil { - end_of_stream = true - return - } - end_of_stream = false - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = 256 - buf := make([]byte, bufsz) - for { - rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) - if rc < 0 { - delete(buf) - return "", _get_errno(rc) - } else if rc == int(bufsz) { - // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? - bufsz *= 2 - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup, err := linux.dup(linux.Fd(fd)) - return Handle(dup), err -} - -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { - buf : [256]byte - fd_str := strconv.write_int( buf[:], cast(i64)fd, 10 ) - - procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } ) - defer delete(procfs_path) - - return _readlink(procfs_path) -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - return strings.clone(string(path_ptr), allocator) -} - -access :: proc(path: string, mask: int) -> (bool, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - result := unix.sys_access(cstr, mask) - if result < 0 { - return false, _get_errno(result) - } - return true, nil -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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 :: proc(key, value: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - key_cstring := strings.clone_to_cstring(key, context.temp_allocator) - value_cstring := strings.clone_to_cstring(value, context.temp_allocator) - // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly - res := _unix_setenv(key_cstring, value_cstring, 1) - if res < 0 { - return get_last_error() - } - return nil -} - -unset_env :: proc(key: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - s := strings.clone_to_cstring(key, context.temp_allocator) - res := _unix_putenv(s) - if res < 0 { - return get_last_error() - } - return nil -} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - context.allocator = allocator - // NOTE(tetra): I would use PATH_MAX here, but I was not able to find - // an authoritative value for it across all systems. - // The largest value I could find was 4096, so might as well use the page size. - page_size := get_page_size() - buf := make([dynamic]u8, page_size) - for { - #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) - - if res >= 0 { - return strings.string_from_null_terminated_ptr(&buf[0], len(buf)) - } - if _get_errno(res) != ERANGE { - delete(buf) - return "" - } - resize(&buf, len(buf)+page_size) - } - unreachable() -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := unix.sys_chdir(cstr) - if res < 0 { - return _get_errno(res) - } - return nil -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(c.int(code)) -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return unix.sys_gettid() -} - -@(require_results) -dlopen :: proc(filename: string, flags: int) -> rawptr { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, c.int(flags)) - return handle -} -@(require_results) -dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { - assert(handle != nil) - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(symbol, context.temp_allocator) - proc_handle := _unix_dlsym(handle, cstr) - return proc_handle -} -dlclose :: proc(handle: rawptr) -> bool { - assert(handle != nil) - return _unix_dlclose(handle) == 0 -} -dlerror :: proc() -> string { - return string(_unix_dlerror()) -} - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - page_size = int(_unix_getpagesize()) - return page_size -} - -@(private, require_results) -_processor_core_count :: proc() -> int { - return int(_unix_get_nprocs()) -} - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for _, i in res { - res[i] = string(runtime.args__[i]) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} - -@(require_results) -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { - result := unix.sys_socket(domain, type, protocol) - if result < 0 { - return 0, _get_errno(result) - } - return Socket(result), nil -} - -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := unix.sys_bind(int(sd), addr, len) - if result < 0 { - return _get_errno(result) - } - return nil -} - - -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { - result := unix.sys_connect(int(sd), addr, len) - if result < 0 { - return _get_errno(result) - } - return nil -} - -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { - result := unix.sys_accept(int(sd), rawptr(addr), len) - if result < 0 { - return 0, _get_errno(result) - } - return Socket(result), nil -} - -listen :: proc(sd: Socket, backlog: int) -> Error { - result := unix.sys_listen(int(sd), backlog) - if result < 0 { - return _get_errno(result) - } - return nil -} - -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { - result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen) - if result < 0 { - return _get_errno(result) - } - return nil -} - - -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { - result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size)) - if result < 0 { - return 0, _get_errno(int(result)) - } - return u32(result), nil -} - -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0) - if result < 0 { - return 0, _get_errno(int(result)) - } - return u32(result), nil -} - - -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { - result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) - if result < 0 { - return 0, _get_errno(int(result)) - } - return u32(result), nil -} - -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0) - if result < 0 { - return 0, _get_errno(int(result)) - } - return u32(result), nil -} - -shutdown :: proc(sd: Socket, how: int) -> Error { - result := unix.sys_shutdown(int(sd), how) - if result < 0 { - return _get_errno(result) - } - return nil -} - -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { - result := unix.sys_fcntl(fd, cmd, arg) - if result < 0 { - return 0, _get_errno(result) - } - return result, nil -} - -@(require_results) -poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) { - result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout) - if result < 0 { - return 0, _get_errno(result) - } - return result, nil -} - -@(require_results) -ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) { - result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t)) - if result < 0 { - return 0, _get_errno(result) - } - return result, nil -} diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin deleted file mode 100644 index 640ea46cd..000000000 --- a/core/os/os_netbsd.odin +++ /dev/null @@ -1,1032 +0,0 @@ -package os - -foreign import dl "system:dl" -foreign import libc "system:c" - -import "base:runtime" -import "core:strings" -import "core:c" - -Handle :: distinct i32 -File_Time :: distinct u64 - -INVALID_HANDLE :: ~Handle(0) - -_Platform_Error :: enum i32 { - NONE = 0, - EPERM = 1, /* Operation not permitted */ - ENOENT = 2, /* No such file or directory */ - EINTR = 4, /* Interrupted system call */ - ESRCH = 3, /* No such process */ - EIO = 5, /* Input/output error */ - ENXIO = 6, /* Device not configured */ - E2BIG = 7, /* Argument list too long */ - ENOEXEC = 8, /* Exec format error */ - EBADF = 9, /* Bad file descriptor */ - ECHILD = 10, /* No child processes */ - EDEADLK = 11, /* Resource deadlock avoided. 11 was EAGAIN */ - ENOMEM = 12, /* Cannot allocate memory */ - EACCES = 13, /* Permission denied */ - EFAULT = 14, /* Bad address */ - ENOTBLK = 15, /* Block device required */ - EBUSY = 16, /* Device busy */ - EEXIST = 17, /* File exists */ - EXDEV = 18, /* Cross-device link */ - ENODEV = 19, /* Operation not supported by device */ - ENOTDIR = 20, /* Not a directory */ - EISDIR = 21, /* Is a directory */ - EINVAL = 22, /* Invalid argument */ - ENFILE = 23, /* Too many open files in system */ - EMFILE = 24, /* Too many open files */ - ENOTTY = 25, /* Inappropriate ioctl for device */ - ETXTBSY = 26, /* Text file busy */ - EFBIG = 27, /* File too large */ - ENOSPC = 28, /* No space left on device */ - ESPIPE = 29, /* Illegal seek */ - EROFS = 30, /* Read-only file system */ - EMLINK = 31, /* Too many links */ - EPIPE = 32, /* Broken pipe */ - - /* math software */ - EDOM = 33, /* Numerical argument out of domain */ - ERANGE = 34, /* Result too large or too small */ - - /* non-blocking and interrupt i/o */ - EAGAIN = 35, /* Resource temporarily unavailable */ - EWOULDBLOCK = EAGAIN, /* Operation would block */ - EINPROGRESS = 36, /* Operation now in progress */ - EALREADY = 37, /* Operation already in progress */ - - /* ipc/network software -- argument errors */ - ENOTSOCK = 38, /* Socket operation on non-socket */ - EDESTADDRREQ = 39, /* Destination address required */ - EMSGSIZE = 40, /* Message too long */ - EPROTOTYPE = 41, /* Protocol wrong type for socket */ - ENOPROTOOPT = 42, /* Protocol option not available */ - EPROTONOSUPPORT = 43, /* Protocol not supported */ - ESOCKTNOSUPPORT = 44, /* Socket type not supported */ - EOPNOTSUPP = 45, /* Operation not supported */ - EPFNOSUPPORT = 46, /* Protocol family not supported */ - EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ - EADDRINUSE = 48, /* Address already in use */ - EADDRNOTAVAIL = 49, /* Can't assign requested address */ - - /* ipc/network software -- operational errors */ - ENETDOWN = 50, /* Network is down */ - ENETUNREACH = 51, /* Network is unreachable */ - ENETRESET = 52, /* Network dropped connection on reset */ - ECONNABORTED = 53, /* Software caused connection abort */ - ECONNRESET = 54, /* Connection reset by peer */ - ENOBUFS = 55, /* No buffer space available */ - EISCONN = 56, /* Socket is already connected */ - ENOTCONN = 57, /* Socket is not connected */ - ESHUTDOWN = 58, /* Can't send after socket shutdown */ - ETOOMANYREFS = 59, /* Too many references: can't splice */ - ETIMEDOUT = 60, /* Operation timed out */ - ECONNREFUSED = 61, /* Connection refused */ - - ELOOP = 62, /* Too many levels of symbolic links */ - ENAMETOOLONG = 63, /* File name too long */ - - /* should be rearranged */ - EHOSTDOWN = 64, /* Host is down */ - EHOSTUNREACH = 65, /* No route to host */ - ENOTEMPTY = 66, /* Directory not empty */ - - /* quotas & mush */ - EPROCLIM = 67, /* Too many processes */ - EUSERS = 68, /* Too many users */ - EDQUOT = 69, /* Disc quota exceeded */ - - /* Network File System */ - ESTALE = 70, /* Stale NFS file handle */ - EREMOTE = 71, /* Too many levels of remote in path */ - EBADRPC = 72, /* RPC struct is bad */ - ERPCMISMATCH = 73, /* RPC version wrong */ - EPROGUNAVAIL = 74, /* RPC prog. not avail */ - EPROGMISMATCH = 75, /* Program version wrong */ - EPROCUNAVAIL = 76, /* Bad procedure for program */ - - ENOLCK = 77, /* No locks available */ - ENOSYS = 78, /* Function not implemented */ - - EFTYPE = 79, /* Inappropriate file type or format */ - EAUTH = 80, /* Authentication error */ - ENEEDAUTH = 81, /* Need authenticator */ - - /* SystemV IPC */ - EIDRM = 82, /* Identifier removed */ - ENOMSG = 83, /* No message of desired type */ - EOVERFLOW = 84, /* Value too large to be stored in data type */ - - /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ - EILSEQ = 85, /* Illegal byte sequence */ - - /* From IEEE Std 1003.1-2001 */ - /* Base, Realtime, Threads or Thread Priority Scheduling option errors */ - ENOTSUP = 86, /* Not supported */ - - /* Realtime option errors */ - ECANCELED = 87, /* Operation canceled */ - - /* Realtime, XSI STREAMS option errors */ - EBADMSG = 88, /* Bad or Corrupt message */ - - /* XSI STREAMS option errors */ - ENODATA = 89, /* No message available */ - ENOSR = 90, /* No STREAM resources */ - ENOSTR = 91, /* Not a STREAM */ - ETIME = 92, /* STREAM ioctl timeout */ - - /* File system extended attribute errors */ - ENOATTR = 93, /* Attribute not found */ - - /* Realtime, XSI STREAMS option errors */ - EMULTIHOP = 94, /* Multihop attempted */ - ENOLINK = 95, /* Link has been severed */ - EPROTO = 96, /* Protocol error */ - - /* Robust mutexes */ - EOWNERDEAD = 97, /* Previous owner died */ - ENOTRECOVERABLE = 98, /* State not recoverable */ - - ELAST = 98, /* Must equal largest Error */ -} - -EPERM :: Platform_Error.EPERM /* Operation not permitted */ -ENOENT :: Platform_Error.ENOENT /* No such file or directory */ -EINTR :: Platform_Error.EINTR /* Interrupted system call */ -ESRCH :: Platform_Error.ESRCH /* No such process */ -EIO :: Platform_Error.EIO /* Input/output error */ -ENXIO :: Platform_Error.ENXIO /* Device not configured */ -E2BIG :: Platform_Error.E2BIG /* Argument list too long */ -ENOEXEC :: Platform_Error.ENOEXEC /* Exec format error */ -EBADF :: Platform_Error.EBADF /* Bad file descriptor */ -ECHILD :: Platform_Error.ECHILD /* No child processes */ -EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock avoided. 11 was EAGAIN */ -ENOMEM :: Platform_Error.ENOMEM /* Cannot allocate memory */ -EACCES :: Platform_Error.EACCES /* Permission denied */ -EFAULT :: Platform_Error.EFAULT /* Bad address */ -ENOTBLK :: Platform_Error.ENOTBLK /* Block device required */ -EBUSY :: Platform_Error.EBUSY /* Device busy */ -EEXIST :: Platform_Error.EEXIST /* File exists */ -EXDEV :: Platform_Error.EXDEV /* Cross-device link */ -ENODEV :: Platform_Error.ENODEV /* Operation not supported by device */ -ENOTDIR :: Platform_Error.ENOTDIR /* Not a directory */ -EISDIR :: Platform_Error.EISDIR /* Is a directory */ -EINVAL :: Platform_Error.EINVAL /* Invalid argument */ -ENFILE :: Platform_Error.ENFILE /* Too many open files in system */ -EMFILE :: Platform_Error.EMFILE /* Too many open files */ -ENOTTY :: Platform_Error.ENOTTY /* Inappropriate ioctl for device */ -ETXTBSY :: Platform_Error.ETXTBSY /* Text file busy */ -EFBIG :: Platform_Error.EFBIG /* File too large */ -ENOSPC :: Platform_Error.ENOSPC /* No space left on device */ -ESPIPE :: Platform_Error.ESPIPE /* Illegal seek */ -EROFS :: Platform_Error.EROFS /* Read-only file system */ -EMLINK :: Platform_Error.EMLINK /* Too many links */ -EPIPE :: Platform_Error.EPIPE /* Broken pipe */ - -/* math software */ -EDOM :: Platform_Error.EDOM /* Numerical argument out of domain */ -ERANGE :: Platform_Error.ERANGE /* Result too large or too small */ - -/* non-blocking and interrupt i/o */ -EAGAIN :: Platform_Error.EAGAIN /* Resource temporarily unavailable */ -EWOULDBLOCK :: EAGAIN /* Operation would block */ -EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ -EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ - -/* ipc/network software -- argument errors */ -ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ -EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ -EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ -EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ -ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol option not available */ -EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ -ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ -EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported */ -EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ -EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol family */ -EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ -EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Can't assign requested address */ - -/* ipc/network software -- operational errors */ -ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ -ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ -ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection on reset */ -ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ -ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ -ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ -EISCONN :: Platform_Error.EISCONN /* Socket is already connected */ -ENOTCONN :: Platform_Error.ENOTCONN /* Socket is not connected */ -ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Can't send after socket shutdown */ -ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: can't splice */ -ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Operation timed out */ -ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ - -ELOOP :: Platform_Error.ELOOP /* Too many levels of symbolic links */ -ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ - -/* should be rearranged */ -EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ -EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ -ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ - -/* quotas & mush */ -EPROCLIM :: Platform_Error.EPROCLIM /* Too many processes */ -EUSERS :: Platform_Error.EUSERS /* Too many users */ -EDQUOT :: Platform_Error.EDQUOT /* Disc quota exceeded */ - -/* Network File System */ -ESTALE :: Platform_Error.ESTALE /* Stale NFS file handle */ -EREMOTE :: Platform_Error.EREMOTE /* Too many levels of remote in path */ -EBADRPC :: Platform_Error.EBADRPC /* RPC struct is bad */ -ERPCMISMATCH :: Platform_Error.ERPCMISMATCH /* RPC version wrong */ -EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL /* RPC prog. not avail */ -EPROGMISMATCH :: Platform_Error.EPROGMISMATCH /* Program version wrong */ -EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL /* Bad procedure for program */ - -ENOLCK :: Platform_Error.ENOLCK /* No locks available */ -ENOSYS :: Platform_Error.ENOSYS /* Function not implemented */ - -EFTYPE :: Platform_Error.EFTYPE /* Inappropriate file type or format */ -EAUTH :: Platform_Error.EAUTH /* Authentication error */ -ENEEDAUTH :: Platform_Error.ENEEDAUTH /* Need authenticator */ - -/* SystemV IPC */ -EIDRM :: Platform_Error.EIDRM /* Identifier removed */ -ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ -EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large to be stored in data type */ - -/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ -EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ - -/* From IEEE Std 1003.1-2001 */ -/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ -ENOTSUP :: Platform_Error.ENOTSUP /* Not supported */ - -/* Realtime option errors */ -ECANCELED :: Platform_Error.ECANCELED /* Operation canceled */ - -/* Realtime, XSI STREAMS option errors */ -EBADMSG :: Platform_Error.EBADMSG /* Bad or Corrupt message */ - -/* XSI STREAMS option errors */ -ENODATA :: Platform_Error.ENODATA /* No message available */ -ENOSR :: Platform_Error.ENOSR /* No STREAM resources */ -ENOSTR :: Platform_Error.ENOSTR /* Not a STREAM */ -ETIME :: Platform_Error.ETIME /* STREAM ioctl timeout */ - -/* File system extended attribute errors */ -ENOATTR :: Platform_Error.ENOATTR /* Attribute not found */ - -/* Realtime, XSI STREAMS option errors */ -EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ -ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ -EPROTO :: Platform_Error.EPROTO /* Protocol error */ - -/* Robust mutexes */ -EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Previous owner died */ -ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ - -ELAST :: Platform_Error.ELAST /* Must equal largest Error */ - -/* end of Error */ - -O_RDONLY :: 0x000000000 -O_WRONLY :: 0x000000001 -O_RDWR :: 0x000000002 -O_CREATE :: 0x000000200 -O_EXCL :: 0x000000800 -O_NOCTTY :: 0x000008000 -O_TRUNC :: 0x000000400 -O_NONBLOCK :: 0x000000004 -O_APPEND :: 0x000000008 -O_SYNC :: 0x000000080 -O_ASYNC :: 0x000000040 -O_CLOEXEC :: 0x000400000 - -RTLD_LAZY :: 0x001 -RTLD_NOW :: 0x002 -RTLD_GLOBAL :: 0x100 -RTLD_LOCAL :: 0x200 -RTLD_TRACE :: 0x200 -RTLD_NODELETE :: 0x01000 -RTLD_NOLOAD :: 0x02000 - -F_GETPATH :: 15 - -MAX_PATH :: 1024 -MAXNAMLEN :: 511 - -args := _alloc_command_line_arguments() - -Unix_File_Time :: struct { - seconds: time_t, - nanoseconds: c.long, -} - -dev_t :: u64 -ino_t :: u64 -nlink_t :: u32 -off_t :: i64 -mode_t :: u32 -pid_t :: u32 -uid_t :: u32 -gid_t :: u32 -blkcnt_t :: i64 -blksize_t :: i32 -fflags_t :: u32 -time_t :: i64 - -OS_Stat :: struct { - device_id: dev_t, - mode: mode_t, - _padding0: i16, - ino: ino_t, - nlink: nlink_t, - uid: uid_t, - gid: gid_t, - _padding1: i32, - rdev: dev_t, - - last_access: Unix_File_Time, - modified: Unix_File_Time, - status_change: Unix_File_Time, - birthtime: Unix_File_Time, - - size: off_t, - blocks: blkcnt_t, - block_size: blksize_t, - - flags: fflags_t, - gen: u32, - lspare: [2]u32, -} - -Dirent :: struct { - ino: ino_t, - reclen: u16, - namlen: u16, - type: u8, - name: [MAXNAMLEN + 1]byte, -} - -Dir :: distinct rawptr // DIR* - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket - -// File mode -// Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - -// Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - -// Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISVTX :: 0o1000 // Directory restrcted delete - -@(require_results) S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } -@(require_results) S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } -@(require_results) S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } -@(require_results) S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } -@(require_results) S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } -@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } -@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0 // Test for file existance -X_OK :: 1 // Test for execute permission -W_OK :: 2 // Test for write permission -R_OK :: 4 // Test for read permission - -foreign libc { - @(link_name="__errno") __errno_location :: proc() -> ^c.int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- - @(link_name="__lstat50") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="__fstat50") _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="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- - @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="__readdir_r30") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- -} - -foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- -} - -@(private) -foreign libc { - _lwp_self :: proc() -> i32 --- -} - -// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - return Platform_Error(__errno_location()^) -} - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), c.uint(mode)) - if handle == -1 { - return INVALID_HANDLE, get_last_error() - } - return handle, nil -} - -close :: proc(fd: Handle) -> Error { - result := _unix_close(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> Error { - result := _unix_fsync(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - to_read := min(c.size_t(len(data)), MAX_RW) - bytes_read := _unix_read(fd, &data[0], to_read) - if bytes_read == -1 { - return -1, get_last_error() - } - return int(bytes_read), nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - bytes_written := _unix_write(fd, &data[0], to_write) - if bytes_written == -1 { - return -1, get_last_error() - } - return int(bytes_written), nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) - if bytes_read < 0 { - return -1, get_last_error() - } - return bytes_read, nil -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) - if bytes_written < 0 { - return -1, get_last_error() - } - return bytes_written, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - res := _unix_seek(fd, offset, c.int(whence)) - if res == -1 { - errno := get_last_error() - switch errno { - case .EINVAL: - return 0, .Invalid_Offset - } - return 0, errno - } - return res, nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (size: i64, err: Error) { - size = -1 - s := _fstat(fd) or_return - size = s.size - return -} - -rename :: proc(old_path, new_path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - res := _unix_rename(old_path_cstr, new_path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -remove :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_unlink(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_mkdir(path_cstr, mode) - if res == -1 { - return get_last_error() - } - return nil -} - -remove_directory :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_rmdir(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -@(require_results) -is_file_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_file_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_dir_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -@(require_results) -is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -is_file :: proc {is_file_path, is_file_handle} -is_dir :: proc {is_dir_path, is_dir_handle} - -@(require_results) -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cpath := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_access(cpath, O_RDONLY) - return res == 0 -} - -@(require_results) -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { - result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) - if result < 0 { - return 0, get_last_error() - } - return int(result), nil -} - -// NOTE(bill): Uses startup to initialize it - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -@(require_results) -last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { - s := _fstat(fd) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { - s := _stat(name) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(private, require_results, no_sanitize_memory) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - s: OS_Stat = --- - result := _unix_lstat(cstr, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_lstat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - s: OS_Stat = --- - result := _unix_fstat(fd, &s) - if result == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - err = nil - - if result == nil { - end_of_stream = true - return - } - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = MAX_PATH - buf := make([]byte, MAX_PATH) - for { - rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) - if rc == -1 { - delete(buf) - return "", get_last_error() - } else if rc == int(bufsz) { - bufsz += MAX_PATH - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } - - return "", Error{} -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup := _unix_dup(fd) - if dup == -1 { - return INVALID_HANDLE, get_last_error() - } - return dup, nil -} - -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { - buf: [MAX_PATH]byte - _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return - return strings.clone_from_cstring(cstring(&buf[0])) -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - return strings.clone(string(path_ptr), allocator) -} - -access :: proc(path: string, mask: int) -> (bool, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - cstr := strings.clone_to_cstring(path, context.temp_allocator) - result := _unix_access(cstr, c.int(mask)) - if result == -1 { - return false, get_last_error() - } - return true, nil -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - context.allocator = allocator - // NOTE(tetra): I would use PATH_MAX here, but I was not able to find - // an authoritative value for it across all systems. - // The largest value I could find was 4096, so might as well use the page size. - page_size := get_page_size() - buf := make([dynamic]u8, page_size) - #no_bounds_check for { - cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) - if cwd != nil { - return string(cwd) - } - if get_last_error() != ERANGE { - delete(buf) - return "" - } - resize(&buf, len(buf)+page_size) - } - unreachable() -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_chdir(cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(c.int(code)) -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return int(_lwp_self()) -} - -@(require_results) -dlopen :: proc(filename: string, flags: int) -> rawptr { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, c.int(flags)) - return handle -} - -@(require_results) -dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { - assert(handle != nil) - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(symbol, context.temp_allocator) - proc_handle := _unix_dlsym(handle, cstr) - return proc_handle -} - -dlclose :: proc(handle: rawptr) -> bool { - assert(handle != nil) - return _unix_dlclose(handle) == 0 -} - -@(require_results) -dlerror :: proc() -> string { - return string(_unix_dlerror()) -} - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - page_size = int(_unix_getpagesize()) - return page_size -} - -@(private, require_results) -_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 -} - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for _, i in res { - res[i] = string(runtime.args__[i]) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin deleted file mode 100644 index bf89a21f4..000000000 --- a/core/os/os_openbsd.odin +++ /dev/null @@ -1,932 +0,0 @@ -package os - -foreign import libc "system:c" - -import "core:strings" -import "core:c" -import "base:runtime" - -Handle :: distinct i32 -Pid :: distinct i32 -File_Time :: distinct u64 - -INVALID_HANDLE :: ~Handle(0) - -_Platform_Error :: enum i32 { - NONE = 0, - EPERM = 1, - ENOENT = 2, - ESRCH = 3, - EINTR = 4, - EIO = 5, - ENXIO = 6, - E2BIG = 7, - ENOEXEC = 8, - EBADF = 9, - ECHILD = 10, - EDEADLK = 11, - ENOMEM = 12, - EACCES = 13, - EFAULT = 14, - ENOTBLK = 15, - EBUSY = 16, - EEXIST = 17, - EXDEV = 18, - ENODEV = 19, - ENOTDIR = 20, - EISDIR = 21, - EINVAL = 22, - ENFILE = 23, - EMFILE = 24, - ENOTTY = 25, - ETXTBSY = 26, - EFBIG = 27, - ENOSPC = 28, - ESPIPE = 29, - EROFS = 30, - EMLINK = 31, - EPIPE = 32, - EDOM = 33, - ERANGE = 34, - EAGAIN = 35, - EWOULDBLOCK = EAGAIN, - EINPROGRESS = 36, - EALREADY = 37, - ENOTSOCK = 38, - EDESTADDRREQ = 39, - EMSGSIZE = 40, - EPROTOTYPE = 41, - ENOPROTOOPT = 42, - EPROTONOSUPPORT = 43, - ESOCKTNOSUPPORT = 44, - EOPNOTSUPP = 45, - EPFNOSUPPORT = 46, - EAFNOSUPPORT = 47, - EADDRINUSE = 48, - EADDRNOTAVAIL = 49, - ENETDOWN = 50, - ENETUNREACH = 51, - ENETRESET = 52, - ECONNABORTED = 53, - ECONNRESET = 54, - ENOBUFS = 55, - EISCONN = 56, - ENOTCONN = 57, - ESHUTDOWN = 58, - ETOOMANYREFS = 59, - ETIMEDOUT = 60, - ECONNREFUSED = 61, - ELOOP = 62, - ENAMETOOLONG = 63, - EHOSTDOWN = 64, - EHOSTUNREACH = 65, - ENOTEMPTY = 66, - EPROCLIM = 67, - EUSERS = 68, - EDQUOT = 69, - ESTALE = 70, - EREMOTE = 71, - EBADRPC = 72, - ERPCMISMATCH = 73, - EPROGUNAVAIL = 74, - EPROGMISMATCH = 75, - EPROCUNAVAIL = 76, - ENOLCK = 77, - ENOSYS = 78, - EFTYPE = 79, - EAUTH = 80, - ENEEDAUTH = 81, - EIPSEC = 82, - ENOATTR = 83, - EILSEQ = 84, - ENOMEDIUM = 85, - EMEDIUMTYPE = 86, - EOVERFLOW = 87, - ECANCELED = 88, - EIDRM = 89, - ENOMSG = 90, - ENOTSUP = 91, - EBADMSG = 92, - ENOTRECOVERABLE = 93, - EOWNERDEAD = 94, - EPROTO = 95, -} - -EPERM :: Platform_Error.EPERM -ENOENT :: Platform_Error.ENOENT -ESRCH :: Platform_Error.ESRCH -EINTR :: Platform_Error.EINTR -EIO :: Platform_Error.EIO -ENXIO :: Platform_Error.ENXIO -E2BIG :: Platform_Error.E2BIG -ENOEXEC :: Platform_Error.ENOEXEC -EBADF :: Platform_Error.EBADF -ECHILD :: Platform_Error.ECHILD -EDEADLK :: Platform_Error.EDEADLK -ENOMEM :: Platform_Error.ENOMEM -EACCES :: Platform_Error.EACCES -EFAULT :: Platform_Error.EFAULT -ENOTBLK :: Platform_Error.ENOTBLK -EBUSY :: Platform_Error.EBUSY -EEXIST :: Platform_Error.EEXIST -EXDEV :: Platform_Error.EXDEV -ENODEV :: Platform_Error.ENODEV -ENOTDIR :: Platform_Error.ENOTDIR -EISDIR :: Platform_Error.EISDIR -EINVAL :: Platform_Error.EINVAL -ENFILE :: Platform_Error.ENFILE -EMFILE :: Platform_Error.EMFILE -ENOTTY :: Platform_Error.ENOTTY -ETXTBSY :: Platform_Error.ETXTBSY -EFBIG :: Platform_Error.EFBIG -ENOSPC :: Platform_Error.ENOSPC -ESPIPE :: Platform_Error.ESPIPE -EROFS :: Platform_Error.EROFS -EMLINK :: Platform_Error.EMLINK -EPIPE :: Platform_Error.EPIPE -EDOM :: Platform_Error.EDOM -ERANGE :: Platform_Error.ERANGE -EAGAIN :: Platform_Error.EAGAIN -EWOULDBLOCK :: Platform_Error.EWOULDBLOCK -EINPROGRESS :: Platform_Error.EINPROGRESS -EALREADY :: Platform_Error.EALREADY -ENOTSOCK :: Platform_Error.ENOTSOCK -EDESTADDRREQ :: Platform_Error.EDESTADDRREQ -EMSGSIZE :: Platform_Error.EMSGSIZE -EPROTOTYPE :: Platform_Error.EPROTOTYPE -ENOPROTOOPT :: Platform_Error.ENOPROTOOPT -EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT -ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT -EOPNOTSUPP :: Platform_Error.EOPNOTSUPP -EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT -EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT -EADDRINUSE :: Platform_Error.EADDRINUSE -EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL -ENETDOWN :: Platform_Error.ENETDOWN -ENETUNREACH :: Platform_Error.ENETUNREACH -ENETRESET :: Platform_Error.ENETRESET -ECONNABORTED :: Platform_Error.ECONNABORTED -ECONNRESET :: Platform_Error.ECONNRESET -ENOBUFS :: Platform_Error.ENOBUFS -EISCONN :: Platform_Error.EISCONN -ENOTCONN :: Platform_Error.ENOTCONN -ESHUTDOWN :: Platform_Error.ESHUTDOWN -ETOOMANYREFS :: Platform_Error.ETOOMANYREFS -ETIMEDOUT :: Platform_Error.ETIMEDOUT -ECONNREFUSED :: Platform_Error.ECONNREFUSED -ELOOP :: Platform_Error.ELOOP -ENAMETOOLONG :: Platform_Error.ENAMETOOLONG -EHOSTDOWN :: Platform_Error.EHOSTDOWN -EHOSTUNREACH :: Platform_Error.EHOSTUNREACH -ENOTEMPTY :: Platform_Error.ENOTEMPTY -EPROCLIM :: Platform_Error.EPROCLIM -EUSERS :: Platform_Error.EUSERS -EDQUOT :: Platform_Error.EDQUOT -ESTALE :: Platform_Error.ESTALE -EREMOTE :: Platform_Error.EREMOTE -EBADRPC :: Platform_Error.EBADRPC -ERPCMISMATCH :: Platform_Error.ERPCMISMATCH -EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL -EPROGMISMATCH :: Platform_Error.EPROGMISMATCH -EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL -ENOLCK :: Platform_Error.ENOLCK -ENOSYS :: Platform_Error.ENOSYS -EFTYPE :: Platform_Error.EFTYPE -EAUTH :: Platform_Error.EAUTH -ENEEDAUTH :: Platform_Error.ENEEDAUTH -EIPSEC :: Platform_Error.EIPSEC -ENOATTR :: Platform_Error.ENOATTR -EILSEQ :: Platform_Error.EILSEQ -ENOMEDIUM :: Platform_Error.ENOMEDIUM -EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE -EOVERFLOW :: Platform_Error.EOVERFLOW -ECANCELED :: Platform_Error.ECANCELED -EIDRM :: Platform_Error.EIDRM -ENOMSG :: Platform_Error.ENOMSG -ENOTSUP :: Platform_Error.ENOTSUP -EBADMSG :: Platform_Error.EBADMSG -ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE -EOWNERDEAD :: Platform_Error.EOWNERDEAD -EPROTO :: Platform_Error.EPROTO - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_NONBLOCK :: 0x00004 -O_APPEND :: 0x00008 -O_ASYNC :: 0x00040 -O_SYNC :: 0x00080 -O_CREATE :: 0x00200 -O_TRUNC :: 0x00400 -O_EXCL :: 0x00800 -O_NOCTTY :: 0x08000 -O_CLOEXEC :: 0x10000 - -RTLD_LAZY :: 0x001 -RTLD_NOW :: 0x002 -RTLD_LOCAL :: 0x000 -RTLD_GLOBAL :: 0x100 -RTLD_TRACE :: 0x200 -RTLD_NODELETE :: 0x400 - -MAX_PATH :: 1024 - -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - -pid_t :: i32 -time_t :: i64 -mode_t :: u32 -dev_t :: i32 -ino_t :: u64 -nlink_t :: u32 -uid_t :: u32 -gid_t :: u32 -off_t :: i64 -blkcnt_t :: u64 -blksize_t :: i32 - -Unix_File_Time :: struct { - seconds: time_t, - nanoseconds: c.long, -} - -OS_Stat :: struct { - mode: mode_t, // inode protection mode - device_id: dev_t, // inode's device - serial: ino_t, // inode's number - nlink: nlink_t, // number of hard links - uid: uid_t, // user ID of the file's owner - gid: gid_t, // group ID of the file's group - rdev: dev_t, // device type - - last_access: Unix_File_Time, // time of last access - modified: Unix_File_Time, // time of last data modification - status_change: Unix_File_Time, // time of last file status change - - size: off_t, // file size, in bytes - blocks: blkcnt_t, // blocks allocated for file - block_size: blksize_t, // optimal blocksize for I/O - - flags: u32, // user defined flags for file - gen: u32, // file generation number - birthtime: Unix_File_Time, // time of file creation -} - -MAXNAMLEN :: 255 - -// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above -Dirent :: struct { - ino: ino_t, // file number of entry - off: off_t, // offset after this entry - reclen: u16, // length of this record - type: u8, // file type - namlen: u8, // length of string in name - _padding: [4]u8, - name: [MAXNAMLEN + 1]byte, // name -} - -Dir :: distinct rawptr // DIR* - -// File type -S_IFMT :: 0o170000 // Type of file mask -S_IFIFO :: 0o010000 // Named pipe (fifo) -S_IFCHR :: 0o020000 // Character special -S_IFDIR :: 0o040000 // Directory -S_IFBLK :: 0o060000 // Block special -S_IFREG :: 0o100000 // Regular -S_IFLNK :: 0o120000 // Symbolic link -S_IFSOCK :: 0o140000 // Socket -S_ISVTX :: 0o001000 // Save swapped text even after use - -// File mode - // Read, write, execute/search by owner -S_IRWXU :: 0o0700 // RWX mask for owner -S_IRUSR :: 0o0400 // R for owner -S_IWUSR :: 0o0200 // W for owner -S_IXUSR :: 0o0100 // X for owner - - // Read, write, execute/search by group -S_IRWXG :: 0o0070 // RWX mask for group -S_IRGRP :: 0o0040 // R for group -S_IWGRP :: 0o0020 // W for group -S_IXGRP :: 0o0010 // X for group - - // Read, write, execute/search by others -S_IRWXO :: 0o0007 // RWX mask for other -S_IROTH :: 0o0004 // R for other -S_IWOTH :: 0o0002 // W for other -S_IXOTH :: 0o0001 // X for other - -S_ISUID :: 0o4000 // Set user id on execution -S_ISGID :: 0o2000 // Set group id on execution -S_ISTXT :: 0o1000 // Sticky bit - -@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } - -F_OK :: 0x00 // Test for file existance -X_OK :: 0x01 // Test for execute permission -W_OK :: 0x02 // Test for write permission -R_OK :: 0x04 // Test for read permission - -AT_FDCWD :: -100 -AT_EACCESS :: 0x01 -AT_SYMLINK_NOFOLLOW :: 0x02 -AT_SYMLINK_FOLLOW :: 0x04 -AT_REMOVEDIR :: 0x08 - -@(default_calling_convention="c") -foreign libc { - @(link_name="__errno") __error :: proc() -> ^c.int --- - - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- - @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- - @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- -} - -@(require_results) -is_path_separator :: proc(r: rune) -> bool { - return r == '/' -} - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - return Platform_Error(__error()^) -} - -@(require_results) -fork :: proc() -> (Pid, Error) { - pid := _unix_fork() - if pid == -1 { - return Pid(-1), get_last_error() - } - return Pid(pid), nil -} - -@(require_results) -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), c.uint(mode)) - if handle == -1 { - return INVALID_HANDLE, get_last_error() - } - return handle, nil -} - -close :: proc(fd: Handle) -> Error { - result := _unix_close(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> Error { - result := _unix_fsync(fd) - if result == -1 { - return get_last_error() - } - return nil -} - -// If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`. -// In practice a read/write call would probably never read/write these big buffers all at once, -// which is why the number of bytes is returned and why there are procs that will call this in a -// loop for you. -// We set a max of 1GB to keep alignment and to be safe. -@(private) -MAX_RW :: 1 << 30 - -read :: proc(fd: Handle, data: []byte) -> (int, Error) { - to_read := min(c.size_t(len(data)), MAX_RW) - bytes_read := _unix_read(fd, &data[0], to_read) - if bytes_read == -1 { - return -1, get_last_error() - } - return int(bytes_read), nil -} - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(c.size_t(len(data)), MAX_RW) - bytes_written := _unix_write(fd, &data[0], to_write) - if bytes_written == -1 { - return -1, get_last_error() - } - return int(bytes_written), nil -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_read := min(uint(len(data)), MAX_RW) - - bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) - if bytes_read < 0 { - return -1, get_last_error() - } - return bytes_read, nil -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - to_write := min(uint(len(data)), MAX_RW) - - bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) - if bytes_written < 0 { - return -1, get_last_error() - } - return bytes_written, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - switch whence { - case SEEK_SET, SEEK_CUR, SEEK_END: - break - case: - return 0, .Invalid_Whence - } - res := _unix_seek(fd, offset, c.int(whence)) - if res == -1 { - errno := get_last_error() - switch errno { - case .EINVAL: - return 0, .Invalid_Offset - } - return 0, errno - } - return res, nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (size: i64, err: Error) { - size = -1 - s := _fstat(fd) or_return - size = s.size - return -} - -rename :: proc(old_path, new_path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - res := _unix_rename(old_path_cstr, new_path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -remove :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_unlink(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_mkdir(path_cstr, mode) - if res == -1 { - return get_last_error() - } - return nil -} - -remove_directory :: proc(path: string) -> Error { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_rmdir(path_cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -@(require_results) -is_file_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_file_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISREG(s.mode) -} - -@(require_results) -is_dir_handle :: proc(fd: Handle) -> bool { - s, err := _fstat(fd) - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -@(require_results) -is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { - s: OS_Stat - err: Error - if follow_links { - s, err = _stat(path) - } else { - s, err = _lstat(path) - } - if err != nil { - return false - } - return S_ISDIR(s.mode) -} - -is_file :: proc {is_file_path, is_file_handle} -is_dir :: proc {is_dir_path, is_dir_handle} - -// NOTE(bill): Uses startup to initialize it - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -/* TODO(zangent): Implement these! -last_write_time :: proc(fd: Handle) -> File_Time {} -last_write_time_by_name :: proc(name: string) -> File_Time {} -*/ -@(require_results) -last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { - s := _fstat(fd) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { - s := _stat(name) or_return - modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), nil -} - -@(private, require_results, no_sanitize_memory) -_stat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_stat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_lstat :: proc(path: string) -> (OS_Stat, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_lstat(cstr, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results, no_sanitize_memory) -_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { - // deliberately uninitialized - s: OS_Stat = --- - res := _unix_fstat(fd, &s) - if res == -1 { - return s, get_last_error() - } - return s, nil -} - -@(private, require_results) -_fdopendir :: proc(fd: Handle) -> (Dir, Error) { - dirp := _unix_fdopendir(fd) - if dirp == cast(Dir)nil { - return nil, get_last_error() - } - return dirp, nil -} - -@(private) -_closedir :: proc(dirp: Dir) -> Error { - rc := _unix_closedir(dirp) - if rc != 0 { - return get_last_error() - } - return nil -} - -@(private) -_rewinddir :: proc(dirp: Dir) { - _unix_rewinddir(dirp) -} - -@(private, require_results) -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { - result: ^Dirent - rc := _unix_readdir_r(dirp, &entry, &result) - - if rc != 0 { - err = get_last_error() - return - } - err = nil - - if result == nil { - end_of_stream = true - return - } - - return -} - -@(private, require_results) -_readlink :: proc(path: string) -> (string, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - - bufsz : uint = MAX_PATH - buf := make([]byte, MAX_PATH) - for { - rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) - if rc == -1 { - delete(buf) - return "", get_last_error() - } else if rc == int(bufsz) { - bufsz += MAX_PATH - delete(buf) - buf = make([]byte, bufsz) - } else { - return strings.string_from_ptr(&buf[0], rc), nil - } - } -} - -@(private, require_results) -_dup :: proc(fd: Handle) -> (Handle, Error) { - dup := _unix_dup(fd) - if dup == -1 { - return INVALID_HANDLE, get_last_error() - } - return dup, nil -} - -// XXX OpenBSD -@(require_results) -absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { - return "", Error(ENOSYS) -} - -@(require_results) -absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { - rel := rel - if rel == "" { - rel = "." - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - - path_ptr := _unix_realpath(rel_cstr, nil) - if path_ptr == nil { - return "", get_last_error() - } - defer _unix_free(rawptr(path_ptr)) - - return strings.clone(string(path_ptr), allocator) -} - -access :: proc(path: string, mask: int) -> (bool, Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_access(cstr, c.int(mask)) - if res == -1 { - return false, get_last_error() - } - return true, nil -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) - // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. - cstr := _unix_getenv(path_str) - if cstr == nil { - return "", false - } - return strings.clone(string(cstr), allocator), true -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - if len(key) + 1 > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, key) - buf[len(key)] = 0 - } - - if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { - return "", .Env_Var_Not_Found - } else { - if len(value) > len(buf) { - return "", .Buffer_Full - } else { - copy(buf, value) - return string(buf[:len(value)]), nil - } - } -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - context.allocator = allocator - buf := make([dynamic]u8, MAX_PATH) - for { - cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) - if cwd != nil { - return string(cwd) - } - if get_last_error() != ERANGE { - delete(buf) - return "" - } - resize(&buf, len(buf) + MAX_PATH) - } - unreachable() -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(path, context.temp_allocator) - res := _unix_chdir(cstr) - if res == -1 { - return get_last_error() - } - return nil -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - _unix_exit(c.int(code)) -} - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return _unix_getthrid() -} - -@(require_results) -dlopen :: proc(filename: string, flags: int) -> rawptr { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, c.int(flags)) - return handle -} -@(require_results) -dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { - assert(handle != nil) - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - cstr := strings.clone_to_cstring(symbol, context.temp_allocator) - proc_handle := _unix_dlsym(handle, cstr) - return proc_handle -} -dlclose :: proc(handle: rawptr) -> bool { - assert(handle != nil) - return _unix_dlclose(handle) == 0 -} -@(require_results) -dlerror :: proc() -> string { - return string(_unix_dlerror()) -} - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - page_size = int(_unix_getpagesize()) - return page_size -} - -_SC_NPROCESSORS_ONLN :: 503 - -@(private, require_results) -_processor_core_count :: proc() -> int { - return int(_sysconf(_SC_NPROCESSORS_ONLN)) -} - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - res := make([]string, len(runtime.args__)) - for _, i in res { - res[i] = string(runtime.args__[i]) - } - return res -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin deleted file mode 100644 index fe0a1fb3e..000000000 --- a/core/os/os_wasi.odin +++ /dev/null @@ -1,273 +0,0 @@ -package os - -import "core:sys/wasm/wasi" -import "base:runtime" - -Handle :: distinct i32 -_Platform_Error :: wasi.errno_t - -INVALID_HANDLE :: -1 - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 - -stdin: Handle = 0 -stdout: Handle = 1 -stderr: Handle = 2 - -args := _alloc_command_line_arguments() - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - cmd_args := make([]string, len(runtime.args__)) - for &arg, i in cmd_args { - arg = string(runtime.args__[i]) - } - return cmd_args -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - delete(args) -} - -// WASI works with "preopened" directories, the environment retrieves directories -// (for example with `wasmtime --dir=. module.wasm`) and those given directories -// are the only ones accessible by the application. -// -// So in order to facilitate the `os` API (absolute paths etc.) we keep a list -// of the given directories and match them when needed (notably `os.open`). - -@(private) -Preopen :: struct { - fd: wasi.fd_t, - prefix: string, -} -@(private) -preopens: []Preopen - -@(init, private) -init_preopens :: proc "contextless" () { - strip_prefixes :: proc "contextless"(path: string) -> string { - path := path - loop: for len(path) > 0 { - switch { - case path[0] == '/': - path = path[1:] - case len(path) > 2 && path[0] == '.' && path[1] == '/': - path = path[2:] - case len(path) == 1 && path[0] == '.': - path = path[1:] - case: - break loop - } - } - return path - } - - context = runtime.default_context() - - dyn_preopens: [dynamic]Preopen - loop: for fd := wasi.fd_t(3); ; fd += 1 { - desc, err := wasi.fd_prestat_get(fd) - #partial switch err { - case .BADF: break loop - case: panic("fd_prestat_get returned an unexpected error") - case .SUCCESS: - } - - switch desc.tag { - case .DIR: - buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens") - if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { - panic("could not get filesystem preopen dir name") - } - append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))}) - } - } - preopens = dyn_preopens[:] -} - -@(require_results) -wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { - @(require_results) - prefix_matches :: proc(prefix, path: string) -> bool { - // Empty is valid for any relative path. - if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { - return true - } - - if len(path) < len(prefix) { - return false - } - - if path[:len(prefix)] != prefix { - return false - } - - // Only match on full components. - i := len(prefix) - for i > 0 && prefix[i-1] == '/' { - i -= 1 - } - return path[i] == '/' - } - - path := path - for len(path) > 0 && path[0] == '/' { - path = path[1:] - } - - match: Preopen - #reverse for preopen in preopens { - if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { - match = preopen - } - } - - if match.fd == 0 { - return 0, "", false - } - - relative := path[len(match.prefix):] - for len(relative) > 0 && relative[0] == '/' { - relative = relative[1:] - } - - if len(relative) == 0 { - relative = "." - } - - return match.fd, relative, true -} - -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { - iovs := wasi.ciovec_t(data) - n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) - return int(n), Platform_Error(err) -} -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - iovs := wasi.iovec_t(data) - n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) - return int(n), Platform_Error(err) -} -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - iovs := wasi.ciovec_t(data) - n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Platform_Error(err) -} -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - iovs := wasi.iovec_t(data) - n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Platform_Error(err) -} -@(require_results) -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { - oflags: wasi.oflags_t - if mode & O_CREATE == O_CREATE { - oflags += {.CREATE} - } - if mode & O_EXCL == O_EXCL { - oflags += {.EXCL} - } - if mode & O_TRUNC == O_TRUNC { - oflags += {.TRUNC} - } - - rights: wasi.rights_t = {.FD_SEEK, .FD_FILESTAT_GET} - switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { - case O_RDONLY: rights += {.FD_READ} - case O_WRONLY: rights += {.FD_WRITE} - case O_RDWR: rights += {.FD_READ, .FD_WRITE} - } - - fdflags: wasi.fdflags_t - if mode & O_APPEND == O_APPEND { - fdflags += {.APPEND} - } - if mode & O_NONBLOCK == O_NONBLOCK { - fdflags += {.NONBLOCK} - } - if mode & O_SYNC == O_SYNC { - fdflags += {.SYNC} - } - - dir_fd, relative, ok := wasi_match_preopen(path) - if !ok { - return INVALID_HANDLE, Errno(wasi.errno_t.BADF) - } - - fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) - return Handle(fd), Platform_Error(err) -} -close :: proc(fd: Handle) -> Errno { - err := wasi.fd_close(wasi.fd_t(fd)) - return Platform_Error(err) -} - -flush :: proc(fd: Handle) -> Error { - // do nothing - return nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) - return i64(n), Platform_Error(err) -} -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return 0 -} -@(private, require_results) -_processor_core_count :: proc() -> int { - return 1 -} - -@(require_results) -file_size :: proc(fd: Handle) -> (size: i64, err: Errno) { - stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return - size = i64(stat.size) - return -} - - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - wasi.proc_exit(wasi.exitcode_t(code)) -} - -@(require_results) -lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return "", false -} - -@(require_results) -lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { - return "", .Env_Var_Not_Found -} -lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} - -@(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} \ No newline at end of file diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin deleted file mode 100644 index cb7e42f67..000000000 --- a/core/os/os_windows.odin +++ /dev/null @@ -1,871 +0,0 @@ -#+build windows -package os - -import win32 "core:sys/windows" -import "base:runtime" -import "base:intrinsics" -import "core:unicode/utf16" - -Handle :: distinct uintptr -File_Time :: distinct u64 - -INVALID_HANDLE :: ~Handle(0) - -O_RDONLY :: 0x00000 -O_WRONLY :: 0x00001 -O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 - -_Platform_Error :: win32.System_Error - -ERROR_FILE_NOT_FOUND :: _Platform_Error(2) -ERROR_PATH_NOT_FOUND :: _Platform_Error(3) -ERROR_ACCESS_DENIED :: _Platform_Error(5) -ERROR_INVALID_HANDLE :: _Platform_Error(6) -ERROR_NOT_ENOUGH_MEMORY :: _Platform_Error(8) -ERROR_NO_MORE_FILES :: _Platform_Error(18) -ERROR_HANDLE_EOF :: _Platform_Error(38) -ERROR_NETNAME_DELETED :: _Platform_Error(64) -ERROR_FILE_EXISTS :: _Platform_Error(80) -ERROR_INVALID_PARAMETER :: _Platform_Error(87) -ERROR_BROKEN_PIPE :: _Platform_Error(109) -ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) -ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) -ERROR_MOD_NOT_FOUND :: _Platform_Error(126) -ERROR_PROC_NOT_FOUND :: _Platform_Error(127) -ERROR_NEGATIVE_SEEK :: _Platform_Error(131) -ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) -ERROR_ALREADY_EXISTS :: _Platform_Error(183) -ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) -ERROR_MORE_DATA :: _Platform_Error(234) -ERROR_OPERATION_ABORTED :: _Platform_Error(995) -ERROR_IO_PENDING :: _Platform_Error(997) -ERROR_NOT_FOUND :: _Platform_Error(1168) -ERROR_PRIVILEGE_NOT_HELD :: _Platform_Error(1314) -WSAEACCES :: _Platform_Error(10013) -WSAECONNRESET :: _Platform_Error(10054) - -ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe -ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir - -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() - -@(require_results, no_instrumentation) -get_last_error :: proc "contextless" () -> Error { - err := win32.GetLastError() - if err == 0 { - return nil - } - switch err { - case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: - return .Permission_Denied - - case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: - return .Exist - - case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: - return .Not_Exist - - case win32.ERROR_NO_DATA: - return .Closed - - case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: - return .Timeout - - case win32.ERROR_NOT_SUPPORTED: - return .Unsupported - - case win32.ERROR_HANDLE_EOF: - return .EOF - - case win32.ERROR_INVALID_HANDLE: - return .Invalid_File - - case win32.ERROR_NEGATIVE_SEEK: - return .Invalid_Offset - - case - win32.ERROR_BAD_ARGUMENTS, - win32.ERROR_INVALID_PARAMETER, - win32.ERROR_NOT_ENOUGH_MEMORY, - win32.ERROR_NO_MORE_FILES, - win32.ERROR_LOCK_VIOLATION, - win32.ERROR_BROKEN_PIPE, - win32.ERROR_CALL_NOT_IMPLEMENTED, - win32.ERROR_INSUFFICIENT_BUFFER, - win32.ERROR_INVALID_NAME, - win32.ERROR_LOCK_FAILED, - win32.ERROR_ENVVAR_NOT_FOUND, - win32.ERROR_OPERATION_ABORTED, - win32.ERROR_IO_PENDING, - win32.ERROR_NO_UNICODE_TRANSLATION: - // fallthrough - } - return Platform_Error(err) -} - - -@(require_results) -last_write_time :: proc(fd: Handle) -> (File_Time, Error) { - file_info: win32.BY_HANDLE_FILE_INFORMATION - if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) { - return 0, get_last_error() - } - lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime) - hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime) - return lo | hi << 32, nil -} - -@(require_results) -last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { - data: win32.WIN32_FILE_ATTRIBUTE_DATA - - wide_path := win32.utf8_to_wstring(name) - if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) { - return 0, get_last_error() - } - - l := File_Time(data.ftLastWriteTime.dwLowDateTime) - h := File_Time(data.ftLastWriteTime.dwHighDateTime) - return l | h << 32, nil -} - - -@(require_results) -get_page_size :: proc() -> int { - // NOTE(tetra): The page size never changes, so why do anything complicated - // if we don't have to. - @static page_size := -1 - if page_size != -1 { - return page_size - } - - info: win32.SYSTEM_INFO - win32.GetSystemInfo(&info) - page_size = int(info.dwPageSize) - return page_size -} - -@(private, require_results) -_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 -} - -exit :: proc "contextless" (code: int) -> ! { - runtime._cleanup_runtime_contextless() - win32.ExitProcess(win32.DWORD(code)) -} - - - -@(require_results) -current_thread_id :: proc "contextless" () -> int { - return int(win32.GetCurrentThreadId()) -} - - - -@(private, require_results) -_alloc_command_line_arguments :: proc "contextless" () -> []string { - context = runtime.default_context() - arg_count: i32 - arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) - arg_list := make([]string, int(arg_count)) - for _, i in arg_list { - wc_str := (^win32.wstring)(uintptr(arg_list_ptr) + size_of(win32.wstring)*uintptr(i))^ - olen := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1, - nil, 0, nil, nil) - - buf := make([]byte, int(olen)) - n := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1, - raw_data(buf), olen, nil, nil) - if n > 0 { - n -= 1 - } - arg_list[i] = string(buf[:n]) - } - - return arg_list -} - -@(private, fini) -_delete_command_line_arguments :: proc "contextless" () { - context = runtime.default_context() - for s in args { - delete(s) - } - delete(args) -} - -/* - Windows 11 (preview) has the same major and minor version numbers - as Windows 10: 10 and 0 respectively. - - To determine if you're on Windows 10 or 11, we need to look at - the build number. As far as we can tell right now, the cutoff is build 22_000. - - TODO: Narrow down this range once Win 11 is published and the last Win 10 builds - become available. -*/ -WINDOWS_11_BUILD_CUTOFF :: 22_000 - -@(require_results) -get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW { - osvi : win32.OSVERSIONINFOEXW - osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW) - win32.RtlGetVersion(&osvi) - return osvi -} - -@(require_results) -is_windows_xp :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) -} - -@(require_results) -is_windows_vista :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) -} - -@(require_results) -is_windows_7 :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) -} - -@(require_results) -is_windows_8 :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) -} - -@(require_results) -is_windows_8_1 :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) -} - -@(require_results) -is_windows_10 :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < WINDOWS_11_BUILD_CUTOFF) -} - -@(require_results) -is_windows_11 :: proc "contextless" () -> bool { - osvi := get_windows_version_w() - return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF) -} - -@(require_results) -is_path_separator :: proc(c: byte) -> bool { - return c == '/' || c == '\\' -} - -@(require_results) -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { - if len(path) == 0 { - return INVALID_HANDLE, General_Error.Not_Exist - } - - access: u32 - switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { - case O_RDONLY: access = win32.FILE_GENERIC_READ - case O_WRONLY: access = win32.FILE_GENERIC_WRITE - case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE - } - - if mode&O_CREATE != 0 { - access |= win32.FILE_GENERIC_WRITE - } - if mode&O_APPEND != 0 { - access &~= win32.FILE_GENERIC_WRITE - access |= win32.FILE_APPEND_DATA - } - - share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE - sa: ^win32.SECURITY_ATTRIBUTES = nil - sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} - if mode&O_CLOEXEC == 0 { - sa = &sa_inherit - } - - create_mode: u32 - switch { - case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): - create_mode = win32.CREATE_NEW - case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): - create_mode = win32.CREATE_ALWAYS - case mode&O_CREATE == O_CREATE: - create_mode = win32.OPEN_ALWAYS - case mode&O_TRUNC == O_TRUNC: - create_mode = win32.TRUNCATE_EXISTING - case: - create_mode = win32.OPEN_EXISTING - } - - attrs := win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS - if mode & (O_NONBLOCK) == O_NONBLOCK { - attrs |= win32.FILE_FLAG_OVERLAPPED - } - - wide_path := win32.utf8_to_wstring(path) - handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, attrs, nil)) - if handle != INVALID_HANDLE { - return handle, nil - } - - return INVALID_HANDLE, get_last_error() -} - -close :: proc(fd: Handle) -> Error { - if !win32.CloseHandle(win32.HANDLE(fd)) { - return get_last_error() - } - return nil -} - -flush :: proc(fd: Handle) -> (err: Error) { - if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - err = get_last_error() - } - return -} - - - -write :: proc(fd: Handle, data: []byte) -> (int, Error) { - if len(data) == 0 { - return 0, nil - } - - single_write_length: win32.DWORD - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := win32.DWORD(min(i32(remaining), MAX_RW)) - - 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), get_last_error() - } - total_write += i64(single_write_length) - } - return int(total_write), nil -} - -@(private="file", require_results) -read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { - if len(b) == 0 { - return 0, nil - } - - BUF_SIZE :: 386 - buf16: [BUF_SIZE]u16 - buf8: [4*BUF_SIZE]u8 - - for n < len(b) && err == nil { - min_read := max(len(b)/4, 1 if len(b) > 0 else 0) - max_read := u32(min(BUF_SIZE, min_read)) - if max_read == 0 { - break - } - - single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = get_last_error() - } - - buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) - src := buf8[:buf8_len] - - ctrl_z := false - for i := 0; i < len(src) && n < len(b); i += 1 { - x := src[i] - if x == 0x1a { // ctrl-z - ctrl_z = true - break - } - b[n] = x - n += 1 - } - if ctrl_z || single_read_length < max_read { - break - } - - // NOTE(bill): if the last two values were a newline, then it is expected that - // this is the end of the input - if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { - break - } - - } - - return -} - -read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { - if len(data) == 0 { - return 0, nil - } - - handle := win32.HANDLE(fd) - - m: u32 - is_console := win32.GetConsoleMode(handle, &m) - length := len(data) - - // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. - to_read := min(i64(length), MAX_RW) - - if is_console { - total_read, err = read_console(handle, data[total_read:][:to_read]) - if err != nil { - return total_read, err - } - } else { - // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) - bytes_read: win32.DWORD - if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e { - // Successful read can mean two things, including EOF, see: - // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file - if bytes_read == 0 { - return 0, .EOF - } else { - return int(bytes_read), nil - } - } else { - return 0, get_last_error() - } - } - return total_read, nil -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { - w: u32 - switch whence { - case 0: w = win32.FILE_BEGIN - case 1: w = win32.FILE_CURRENT - case 2: w = win32.FILE_END - case: - return 0, .Invalid_Whence - } - hi := i32(offset>>32) - lo := i32(offset) - ft := win32.GetFileType(win32.HANDLE(fd)) - if ft == win32.FILE_TYPE_PIPE { - return 0, .File_Is_Pipe - } - - dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) - if dw_ptr == win32.INVALID_SET_FILE_POINTER { - err := get_last_error() - return 0, err - } - return i64(hi)<<32 + i64(dw_ptr), nil -} - -@(require_results) -file_size :: proc(fd: Handle) -> (i64, Error) { - length: win32.LARGE_INTEGER - err: Error - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = get_last_error() - } - return i64(length), err -} - - -@(private) -MAX_RW :: 1<<30 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - // TODO(bill): Determine the correct behaviour for consoles - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Error - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = get_last_error() - done = 0 - } - return int(done), e -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Error - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = get_last_error() - done = 0 - } - return int(done), e -} - -/* -read_at returns n: 0, err: 0 on EOF -*/ -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := data, offset - for len(b) > 0 { - m, e := pread(fd, b, offset) - if e == ERROR_EOF { - err = nil - break - } - if e != nil { - err = e - break - } - n += m - b = b[m:] - offset += i64(m) - } - return -} - -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { - if offset < 0 { - return 0, .Invalid_Offset - } - - b, offset := data, offset - for len(b) > 0 { - m := pwrite(fd, b, offset) or_return - n += m - b = b[m:] - offset += i64(m) - } - return -} - - - -// NOTE(bill): Uses startup to initialize it -stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE)) -stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE)) -stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE)) - - -@(require_results) -get_std_handle :: proc "contextless" (h: uint) -> Handle { - fd := win32.GetStdHandle(win32.DWORD(h)) - return Handle(fd) -} - - -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - return attribs != win32.INVALID_FILE_ATTRIBUTES -} - -@(require_results) -is_file :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -@(require_results) -is_dir :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -@private cwd_lock := win32.SRWLOCK{} // zero is initialized - -@(require_results) -get_current_directory :: proc(allocator := context.allocator) -> string { - win32.AcquireSRWLockExclusive(&cwd_lock) - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - - sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. - - sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) - assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" -} - -set_current_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wstr := win32.utf8_to_wstring(path, context.temp_allocator) - - win32.AcquireSRWLockExclusive(&cwd_lock) - - if !win32.SetCurrentDirectoryW(wstr) { - err = get_last_error() - } - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return -} -change_directory :: set_current_directory - -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - // Mode is unused on Windows, but is needed on *nix - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.CreateDirectoryW(wpath, nil) { - err = get_last_error() - } - return -} - - -remove_directory :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.RemoveDirectoryW(wpath) { - err = get_last_error() - } - return -} - - - -@(private, require_results) -is_abs :: proc(path: string) -> bool { - if len(path) > 0 && path[0] == '/' { - return true - } - when ODIN_OS == .Windows { - if len(path) > 2 { - switch path[0] { - case 'A'..='Z', 'a'..='z': - return path[1] == ':' && is_path_separator(path[2]) - } - } - } - return false -} - -@(private, require_results) -fix_long_path :: proc(path: string) -> string { - if len(path) < 248 { - return path - } - - if len(path) >= 2 && path[:2] == `\\` { - return path - } - if !is_abs(path) { - return path - } - - prefix :: `\\?` - - path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) - copy(path_buf, prefix) - n := len(path) - r, w := 0, len(prefix) - for r < n { - switch { - case is_path_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): - r += 1 - case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): - return path - case: - path_buf[w] = '\\' - w += 1 - for ; r < n && !is_path_separator(path[r]); r += 1 { - path_buf[w] = path[r] - w += 1 - } - } - } - - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 - } - return string(path_buf[:w]) -} - - -link :: proc(old_name, new_name: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - n := win32.utf8_to_wstring(fix_long_path(new_name)) - o := win32.utf8_to_wstring(fix_long_path(old_name)) - return Platform_Error(win32.CreateHardLinkW(n, o, nil)) -} - -unlink :: proc(path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.DeleteFileW(wpath) { - err = get_last_error() - } - return -} - - - -rename :: proc(old_path, new_path: string) -> (err: Error) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - from := win32.utf8_to_wstring(old_path, context.temp_allocator) - to := win32.utf8_to_wstring(new_path, context.temp_allocator) - - if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - err = get_last_error() - } - return -} - - -ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { - curr_off := seek(fd, 0, 1) or_return - defer seek(fd, curr_off, 0) - _= seek(fd, length, 0) or_return - ok := win32.SetEndOfFile(win32.HANDLE(fd)) - if !ok { - return get_last_error() - } - return nil -} - -truncate :: proc(path: string, length: i64) -> (err: Error) { - fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return - defer close(fd) - return ftruncate(fd, length) -} - - -remove :: proc(name: string) -> Error { - p := win32.utf8_to_wstring(fix_long_path(name)) - err, err1: win32.DWORD - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - if err == 0 { - return nil - } - if !win32.RemoveDirectoryW(p) { - err1 = win32.GetLastError() - } - if err1 == 0 { - return nil - } - - if err != err1 { - a := win32.GetFileAttributesW(p) - if a == ~u32(0) { - err = win32.GetLastError() - } else { - if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - err = err1 - } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { - if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = 0 - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - } - } - } - } - - return Platform_Error(err) -} - - -@(require_results) -pipe :: proc() -> (r, w: Handle, err: Error) { - sa: win32.SECURITY_ATTRIBUTES - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true - if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { - err = get_last_error() - } - return -} diff --git a/core/os/path.odin b/core/os/path.odin new file mode 100644 index 000000000..ac18b7562 --- /dev/null +++ b/core/os/path.odin @@ -0,0 +1,980 @@ +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.") + +/* +Return true if `c` is a character used to separate paths into directory and +file hierarchies on the current system. +*/ +@(require_results) +is_path_separator :: proc(c: byte) -> bool { + return _is_path_separator(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 + +/* +Make a new directory. + +If `path` is relative, it will be relative to the process's current working directory. +*/ +make_directory :: proc(name: string, perm: int = 0o755) -> Error { + return _mkdir(name, perm) +} + +mkdir_all :: make_directory_all + +/* +Make a new directory, creating new intervening directories when needed. + +If `path` is relative, it will be relative to the process's current working directory. +*/ +make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { + return _mkdir_all(path, perm) +} + +/* +Delete `path` and all files and directories inside of `path` if it is a directory. + +If `path` is relative, it will be relative to the process's current working directory. +*/ +remove_all :: proc(path: string) -> Error { + return _remove_all(path) +} + +getwd :: get_working_directory + +/* +Get the working directory of the current process. + +*Allocates Using Provided Allocator* +*/ +@(require_results) +get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _get_working_directory(allocator) +} + +setwd :: set_working_directory + +/* +Change the working directory of the current process. + +*Allocates Using Provided Allocator* +*/ +set_working_directory :: proc(dir: string) -> (err: Error) { + return _set_working_directory(dir) +} + +/* +Get the path for the currently running executable. + +*Allocates Using Provided Allocator* +*/ +@(require_results) +get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + return _get_executable_path(allocator) +} + +/* +Get the directory for the currently running executable. + +*Allocates Using Provided Allocator* +*/ +@(require_results) +get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + path = _get_executable_path(allocator) or_return + path, _ = split_path(path) + return +} + +/* +Compare two paths for exactness without normalization. + +This procedure takes into account case-sensitivity on differing systems. +*/ +@(require_results) +are_paths_identical :: proc(a, b: string) -> (identical: bool) { + return _are_paths_identical(a, b) +} + +/* +Normalize a path. + +*Allocates Using Provided Allocator* + +This will remove duplicate separators and unneeded references to the current or +parent directory. +*/ +@(require_results) +clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) { + if path == "" || path == "." { + return strings.clone(".", allocator) + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + // The extra byte is to simplify appending path elements by letting the + // loop to end each with a separator. We'll trim the last one when we're done. + buffer := make([]u8, len(path) + 1, temp_allocator) or_return + + // This is the only point where Windows and POSIX differ, as Windows has + // alphabet-based volumes for root paths. + rooted, start := _clean_path_handle_start(path, buffer) + + head, buffer_i := start, start + for i, j := start, start; i <= len(path); i += 1 { + if i == len(path) || _is_path_separator(path[i]) { + elem := path[j:i] + j = i + 1 + + switch elem { + case "", ".": + // Skip duplicate path separators and current directory references. + case "..": + if !rooted && buffer_i == head { + // Only allow accessing further parent directories when the path is relative. + buffer[buffer_i] = '.' + buffer[buffer_i+1] = '.' + buffer[buffer_i+2] = _Path_Separator + buffer_i += 3 + head = buffer_i + } else { + // Roll back to the last separator or the head of the buffer. + back_to := head + // `buffer_i` will be equal to 1 + the last set byte, so + // skipping two bytes avoids the final separator we just + // added. + for k := buffer_i-2; k >= head; k -= 1 { + if _is_path_separator(buffer[k]) { + back_to = k + 1 + break + } + } + buffer_i = back_to + } + case: + // Copy the path element verbatim and add a separator. + copy(buffer[buffer_i:], elem) + buffer_i += len(elem) + buffer[buffer_i] = _Path_Separator + buffer_i += 1 + } + } + } + + // Trim the final separator. + // NOTE: No need to check if the last byte is a separator, as we always add it. + if buffer_i > start { + buffer_i -= 1 + } + + if buffer_i == 0 { + return strings.clone(".", allocator) + } + + compact := make([]u8, buffer_i, allocator) or_return + copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here + return string(compact), nil +} + +/* +Return true if `path` is an absolute path as opposed to a relative one. +*/ +@(require_results) +is_absolute_path :: proc(path: string) -> bool { + return _is_absolute_path(path) +} + +/* +Get the absolute path to `path` with respect to the process's current directory. + +*Allocates Using Provided Allocator* +*/ +@(require_results) +get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + return _get_absolute_path(path, allocator) +} + +/* +Get the relative path needed to change directories from `base` to `target`. + +*Allocates Using Provided Allocator* + +The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`. + +NOTE: This procedure expects both `base` and `target` to be normalized first, +which can be done by calling `clean_path` on them if needed. + +This procedure will return an `Invalid_Path` error if `base` begins with a +reference to the parent directory (`".."`). Use `get_working_directory` with +`join_path` to construct absolute paths for both arguments instead. +*/ +@(require_results) +get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) { + if _are_paths_identical(base, target) { + return strings.clone(".", allocator) + } + if base == "." { + return strings.clone(target, allocator) + } + + // This is the first point where Windows and POSIX differ, as Windows has + // alphabet-based volumes for root paths. + if !_get_relative_path_handle_start(base, target) { + return "", .Invalid_Path + } + if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) { + // We could do the work for the user of getting absolute paths for both + // arguments, but that could make something costly (repeatedly + // normalizing paths) convenient, when it would be better for the user + // to store already-finalized paths and operate on those instead. + return "", .Invalid_Path + } + + // This is the other point where Windows and POSIX differ, as Windows is + // case-insensitive. + common := _get_common_path_len(base, target) + + // Get the result of splitting `base` and `target` on _Path_Separator, + // comparing them up to their most common elements, then count how many + // unshared parts are in the split `base`. + seps := 0 + size := 0 + if len(base)-common > 0 { + seps = 1 + size = 2 + } + // This range skips separators on the ends of the string. + for i in common+1.. 0 { + // Account for leading separators on the target after cutting the common part. + // (i.e. base == `/home`, target == `/home/a`) + if _is_path_separator(trailing[0]) { + trailing = trailing[1:] + } + size += len(trailing) + if seps > 0 { + size += 1 + } + } + if trailing == "." { + trailing = "" + size -= 2 + } + + // Build the string. + buf := make([]u8, size, allocator) or_return + n := 0 + if seps > 0 { + buf[0] = '.' + buf[1] = '.' + n = 2 + } + for _ in 1.. 0 { + if seps > 0 { + buf[n] = _Path_Separator + n += 1 + } + copy(buf[n:], trailing) + } + + path = string(buf) + + return +} + +/* +Split a path into a directory hierarchy and a filename. + +For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`. +*/ +@(require_results) +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. + +*Allocates Using Provided Allocator* + +For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`. +*/ +@(require_results) +join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { + for e, i in elems { + if e != "" { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return + return clean_path(p, allocator) + } + } + return "", nil +} + +/* +Split a filename from its extension. + +This procedure splits on the last separator. + +If the filename begins with a separator, such as `".readme.txt"`, the separator +will be included in the filename, resulting in `".readme"` and `"txt"`. + +For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`. +*/ +@(require_results) +split_filename :: proc(filename: string) -> (base, ext: string) { + i := strings.last_index_byte(filename, '.') + if i <= 0 { + return filename, "" + } + return filename[:i], filename[i+1:] +} + +/* +Split a filename from its extension. + +This procedure splits on the first separator. + +If the filename begins with a separator, such as `".readme.txt.gz"`, the separator +will be included in the filename, resulting in `".readme"` and `"txt.gz"`. + +For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`. +*/ +@(require_results) +split_filename_all :: proc(filename: string) -> (base, ext: string) { + i := strings.index_byte(filename, '.') + if i == 0 { + j := strings.index_byte(filename[1:], '.') + if j != -1 { + j += 1 + } + i = j + } + if i == -1 { + return filename, "" + } + return filename[:i], filename[i+1:] +} + +/* +Join `base` and `ext` with the system's filename extension separator. + +*Allocates Using Provided Allocator* + +For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`. +*/ +@(require_results) +join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) { + if len(base) == 0 { + return strings.clone(ext, allocator) + } else if len(ext) == 0 { + return strings.clone(base, allocator) + } + + buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return + copy(buf, base) + buf[len(base)] = '.' + copy(buf[1+len(base):], ext) + + return string(buf), nil +} + +/* +Split a string that is separated by a system-specific separator, typically used +for environment variables specifying multiple directories. + +*Allocates Using Provided Allocator* + +For example, there is the "PATH" environment variable on POSIX systems which +this procedure can split into separate entries. +*/ +@(require_results) +split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: 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 == Path_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 == Path_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 +} + +/* +`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) { + _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) { + // TODO(bill): os.lstat on here to check for error + m := make([]string, 1) + m[0] = pattern + return m[:], nil + } + + // NOTE(Jeroen): For `glob`, we need this version of `split`, which leaves the trailing `/` on `dir`. + dir, file := _split(pattern) + + temp_buf: [8]byte + vol_len: int + vol_len, dir = clean_glob_path(dir, temp_buf[:]) + + if !has_meta(dir[vol_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) { + 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 { + 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 !slash_equal(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) -> (int, 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/path_darwin.odin b/core/os/path_darwin.odin new file mode 100644 index 000000000..65aaf1e95 --- /dev/null +++ b/core/os/path_darwin.odin @@ -0,0 +1,17 @@ +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + return clone_string(string(buffer[:ret]), allocator) + } + + err = _get_platform_error() + return +} diff --git a/core/os/path_freebsd.odin b/core/os/path_freebsd.odin new file mode 100644 index 000000000..e7e4f63c9 --- /dev/null +++ b/core/os/path_freebsd.odin @@ -0,0 +1,29 @@ +package os2 + +import "base:runtime" + +import "core:sys/freebsd" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)} + + size: uint + if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + assert(size > 0) + + buf := make([]byte, size, allocator) or_return + defer if err != nil { delete(buf, allocator) } + + assert(uint(len(buf)) == size) + + if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + + return string(buf[:size-1]), nil +} diff --git a/core/os/path_js.odin b/core/os/path_js.odin new file mode 100644 index 000000000..0c0d1424b --- /dev/null +++ b/core/os/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/path_linux.odin b/core/os/path_linux.odin new file mode 100644 index 000000000..1c9927843 --- /dev/null +++ b/core/os/path_linux.odin @@ -0,0 +1,227 @@ +#+private +package os2 + +import "base:runtime" + +import "core:strings" +import "core:strconv" +import "core:sys/linux" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC} + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(path: string, perm: int) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return + return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { + i: int + for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} + if i == 0 { + return _get_platform_error(linux.close(dfd)) + } + path[i] = 0 + new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) + #partial switch errno { + case .ENOENT: + if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE { + return _get_platform_error(errno) + } + has_created^ = true + if new_dfd, errno = linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); errno != .NONE { + return _get_platform_error(errno) + } + fallthrough + case .NONE: + if errno = linux.close(dfd); errno != .NONE { + return _get_platform_error(errno) + } + // skip consecutive '/' + for i += 1; i < len(path) && path[i] == '/'; i += 1 {} + return mkdirat(new_dfd, path[i:], perm, has_created) + } + return _get_platform_error(errno) + } + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + // need something we can edit, and use to generate cstrings + path_bytes := make([]u8, len(path) + 1, temp_allocator) + + // zero terminate the byte slice to make it a valid cstring + copy(path_bytes, path) + path_bytes[len(path)] = 0 + + dfd: linux.Fd + errno: linux.Errno + if path_bytes[0] == '/' { + dfd, errno = linux.open("/", _OPENDIR_FLAGS) + path_bytes = path_bytes[1:] + } else { + dfd, errno = linux.open(".", _OPENDIR_FLAGS) + } + if errno != .NONE { + return _get_platform_error(errno) + } + + has_created: bool + mkdirat(dfd, path_bytes, perm, &has_created) or_return + return nil if has_created else .Exist +} + +_remove_all :: proc(path: string) -> Error { + remove_all_dir :: proc(dfd: linux.Fd) -> Error { + n := 64 + buf := make([]u8, n) + defer delete(buf) + + loop: for { + buflen, errno := linux.getdents(dfd, buf[:]) + #partial switch errno { + case .EINVAL: + delete(buf) + n *= 2 + buf = make([]u8, n) + continue loop + case .NONE: + if buflen == 0 { break loop } + case: + return _get_platform_error(errno) + } + + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + d_name_cstr := strings.unsafe_string_to_cstring(d_name_str) + + /* check for current or parent directory (. or ..) */ + if d_name_str == "." || d_name_str == ".." { + continue + } + + #partial switch d.type { + case .DIR: + new_dfd: linux.Fd + new_dfd, errno = linux.openat(dfd, d_name_cstr, _OPENDIR_FLAGS) + if errno != .NONE { + return _get_platform_error(errno) + } + defer linux.close(new_dfd) + remove_all_dir(new_dfd) or_return + errno = linux.unlinkat(dfd, d_name_cstr, {.REMOVEDIR}) + case: + errno = linux.unlinkat(dfd, d_name_cstr, nil) + } + + if errno != .NONE { + return _get_platform_error(errno) + } + } + } + return nil + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return + + fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + break + case .ENOTDIR: + return _get_platform_error(linux.unlink(path_cstr)) + case: + return _get_platform_error(errno) + } + + defer linux.close(fd) + remove_all_dir(fd) or_return + return _get_platform_error(linux.rmdir(path_cstr)) +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + // NOTE(jason): Avoiding libc, so just use 4096 directly + PATH_MAX :: 4096 + buf := make([dynamic]u8, PATH_MAX, allocator) + for { + #no_bounds_check n, errno := linux.getcwd(buf[:]) + if errno == .NONE { + return string(buf[:n-1]), nil + } + if errno != .ERANGE { + return "", _get_platform_error(errno) + } + resize(&buf, len(buf)+PATH_MAX) + } + unreachable() +} + +_set_working_directory :: proc(dir: string) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + dir_cstr := clone_to_cstring(dir, temp_allocator) or_return + return _get_platform_error(linux.chdir(dir_cstr)) +} + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := make([dynamic]byte, 1024, temp_allocator) or_return + for { + n, errno := linux.readlink("/proc/self/exe", buf[:]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} + +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { + PROC_FD_PATH :: "/proc/self/fd/" + + buf: [32]u8 + copy(buf[:], PROC_FD_PATH) + + strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10) + + if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { + delete(fullpath, allocator) + fullpath = "" + } + return +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {}) + if errno != nil { + err = _get_platform_error(errno) + return + } + defer linux.close(fd) + + return _get_full_path(fd, allocator) +} diff --git a/core/os/path_netbsd.odin b/core/os/path_netbsd.odin new file mode 100644 index 000000000..815102dea --- /dev/null +++ b/core/os/path_netbsd.odin @@ -0,0 +1,24 @@ +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := make([dynamic]byte, 1024, temp_allocator) or_return + for { + n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) + if n < 0 { + err = _get_platform_error() + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} diff --git a/core/os/path_openbsd.odin b/core/os/path_openbsd.odin new file mode 100644 index 000000000..cbc0346d4 --- /dev/null +++ b/core/os/path_openbsd.odin @@ -0,0 +1,57 @@ +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + // OpenBSD does not have an API for this, we do our best below. + + if len(runtime.args__) <= 0 { + err = .Invalid_Path + return + } + + real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) { + real := posix.realpath(path) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + return clone_string(string(real), allocator) + } + + arg := runtime.args__[0] + sarg := string(arg) + + if len(sarg) == 0 { + err = .Invalid_Path + return + } + + if sarg[0] == '.' || sarg[0] == '/' { + return real(arg, allocator) + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := strings.builder_make(temp_allocator) + + paths := get_env("PATH", temp_allocator) + for dir in strings.split_iterator(&paths, ":") { + strings.builder_reset(&buf) + strings.write_string(&buf, dir) + strings.write_string(&buf, "/") + strings.write_string(&buf, sarg) + + cpath := strings.to_cstring(&buf) or_return + if posix.access(cpath, {.X_OK}) == .OK { + return real(cpath, allocator) + } + } + + err = .Invalid_Path + return +} diff --git a/core/os/path_posix.odin b/core/os/path_posix.odin new file mode 100644 index 000000000..173cb6b6d --- /dev/null +++ b/core/os/path_posix.odin @@ -0,0 +1,142 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return + if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { + return _get_platform_error() + } + return nil +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + if exists(path) { + return .Exist + } + + clean_path := clean_path(path, temp_allocator) or_return + return internal_mkdir_all(clean_path, perm) + + internal_mkdir_all :: proc(path: string, perm: int) -> Error { + dir, file := split_path(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir, perm) or_return + } + + err := _mkdir(path, perm) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath := clone_to_cstring(path, temp_allocator) or_return + + dir := posix.opendir(cpath) + if dir == nil { + return _get_platform_error() + } + defer posix.closedir(dir) + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + return _get_platform_error() + } else { + break + } + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator) + if entry.d_type == .DIR { + _remove_all(fullpath[:len(fullpath)-1]) or_return + } else { + if posix.unlink(cstring(raw_data(fullpath))) != .OK { + return _get_platform_error() + } + } + } + + if posix.rmdir(cpath) != .OK { + return _get_platform_error() + } + return nil +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf: [dynamic]byte + buf.allocator = temp_allocator + size := uint(posix.PATH_MAX) + + cwd: cstring + for ; cwd == nil; size *= 2 { + resize(&buf, size) + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil && posix.errno() != .ERANGE { + err = _get_platform_error() + return + } + } + + return clone_string(string(cwd), allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cdir := clone_to_cstring(dir, temp_allocator) or_return + if posix.chdir(cdir) != .OK { + err = _get_platform_error() + } + return +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_cstr := clone_to_cstring(rel, temp_allocator) or_return + path_ptr := posix.realpath(rel_cstr, nil) + if path_ptr == nil { + return "", _get_platform_error() + } + defer posix.free(path_ptr) + + path_str := clone_string(string(path_ptr), allocator) or_return + return path_str, nil +} diff --git a/core/os/path_posixfs.odin b/core/os/path_posixfs.odin new file mode 100644 index 000000000..0736e73d1 --- /dev/null +++ b/core/os/path_posixfs.odin @@ -0,0 +1,57 @@ +#+private +#+build linux, darwin, netbsd, freebsd, openbsd, wasi +package os2 + +// This implementation is for all systems that have POSIX-compliant filesystem paths. + +_are_paths_identical :: proc(a, b: string) -> (identical: bool) { + return a == b +} + +_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { + // Preserve rooted paths. + if _is_path_separator(path[0]) { + rooted = true + buffer[0] = _Path_Separator + start = 1 + } + return +} + +_is_absolute_path :: proc(path: string) -> bool { + return len(path) > 0 && _is_path_separator(path[0]) +} + +_get_relative_path_handle_start :: proc(base, target: string) -> bool { + base_rooted := len(base) > 0 && _is_path_separator(base[0]) + target_rooted := len(target) > 0 && _is_path_separator(target[0]) + return base_rooted == target_rooted +} + +_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 +} diff --git a/core/os/path_wasi.odin b/core/os/path_wasi.odin new file mode 100644 index 000000000..f26e16158 --- /dev/null +++ b/core/os/path_wasi.odin @@ -0,0 +1,120 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sync" +import "core:sys/wasm/wasi" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_create_directory(dir_fd, relative)) +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + if exists(path) { + return .Exist + } + + clean_path := clean_path(path, temp_allocator) or_return + return internal_mkdir_all(clean_path) + + internal_mkdir_all :: proc(path: string) -> Error { + dir, file := split_path(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir) or_return + } + + err := _mkdir(path, 0) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> (err: Error) { + // PERF: this works, but wastes a bunch of memory using the read_directory_iterator API + // and using open instead of wasi fds directly. + { + dir := open(path) or_return + defer close(dir) + + iter := read_directory_iterator_create(dir) + defer read_directory_iterator_destroy(&iter) + + for fi in read_directory_iterator(&iter) { + _ = read_directory_iterator_error(&iter) or_break + + if fi.type == .Directory { + _remove_all(fi.fullpath) or_return + } else { + remove(fi.fullpath) or_return + } + } + + _ = read_directory_iterator_error(&iter) or_return + } + + return remove(path) +} + +g_wd: string +g_wd_mutex: sync.Mutex + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + sync.guard(&g_wd_mutex) + + return clone_string(g_wd if g_wd != "" else "/", allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + sync.guard(&g_wd_mutex) + + if dir == g_wd { + return + } + + if g_wd != "" { + delete(g_wd, file_allocator()) + } + + g_wd = clone_string(dir, file_allocator()) or_return + return +} + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + if len(args) <= 0 { + return clone_string("/", allocator) + } + + arg := args[0] + if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') { + return clone_string(arg, allocator) + } + + return concatenate({"/", arg}, allocator) +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + return "", .Unsupported +} diff --git a/core/os/path_windows.odin b/core/os/path_windows.odin new file mode 100644 index 000000000..275fe3e18 --- /dev/null +++ b/core/os/path_windows.odin @@ -0,0 +1,359 @@ +#+private +package os2 + +import "base:runtime" +import "core:strings" +import win32 "core:sys/windows" + +_Path_Separator :: '\\' +_Path_Separator_String :: "\\" +_Path_List_Separator :: ';' + +_is_path_separator :: proc(c: byte) -> bool { + return c == '\\' || c == '/' +} + +_mkdir :: proc(name: string, perm: int) -> Error { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) { + return _get_platform_error() + } + return nil +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { + if len(p) == len(`\\?\c:`) { + if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { + s = concatenate({p, `\`}, file_allocator()) or_return + allocated = true + return + } + } + return p, false, nil + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + dir_stat, err := stat(path, temp_allocator) + if err == nil { + if dir_stat.type == .Directory { + return nil + } + return .Exist + } + + i := len(path) + for i > 0 && is_path_separator(path[i-1]) { + i -= 1 + } + + j := i + for j > 0 && !is_path_separator(path[j-1]) { + j -= 1 + } + + if j > 1 { + new_path, allocated := fix_root_directory(path[:j-1]) or_return + defer if allocated { + delete(new_path, file_allocator()) + } + mkdir_all(new_path, perm) or_return + } + + err = mkdir(path, perm) + if err != nil { + new_dir_stat, err1 := lstat(path, temp_allocator) + if err1 == nil && new_dir_stat.type == .Directory { + return nil + } + return err + } + return nil +} + +_remove_all :: proc(path: string) -> Error { + if path == "" { + return nil + } + + err := remove(path) + if err == nil || err == .Not_Exist { + return nil + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := win32_utf8_to_wstring(path, temp_allocator) or_return + + empty: [1]u16 + + file_op := win32.SHFILEOPSTRUCTW { + nil, + win32.FO_DELETE, + dir, + cstring16(&empty[0]), + win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, + false, + nil, + cstring16(&empty[0]), + } + res := win32.SHFileOperationW(&file_op) + if res != 0 { + return _get_platform_error() + } + return nil +} + +@private cwd_lock: win32.SRWLOCK // zero is initialized + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + win32.AcquireSRWLockExclusive(&cwd_lock) + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32_utf16_to_utf8(dir_buf_wstr, allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return + + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = _get_platform_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buf := make([dynamic]u16, 512, temp_allocator) or_return + for { + ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) + if ret == 0 { + err = _get_platform_error() + return + } + + if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER { + resize(&buf, len(buf)*2) or_return + continue + } + + return win32_utf16_to_utf8(buf[:ret], allocator) + } +} + +@(private) +can_use_long_paths: bool + +@(init) +init_long_path_support :: proc "contextless" () { + can_use_long_paths = false + + key: win32.HKEY + res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key) + defer win32.RegCloseKey(key) + if res != 0 { + return + } + + value: u32 + size := u32(size_of(value)) + res = win32.RegGetValueW( + key, + nil, + win32.L("LongPathsEnabled"), + win32.RRF_RT_ANY, + nil, + &value, + &size, + ) + if res != 0 { + return + } + if value == 1 { + can_use_long_paths = true + } +} + +@(require_results) +_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) { + return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator) +} + +@(require_results) +_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) { + return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator) +} + +@(require_results) +_fix_long_path_internal :: proc(path: string) -> string { + if can_use_long_paths { + return path + } + + // When using win32 to create a directory, the path + // cannot be too long that you cannot append an 8.3 + // file name, because MAX_PATH is 260, 260-12 = 248 + if len(path) < 248 { + return path + } + + // UNC paths do not need to be modified + if len(path) >= 2 && path[:2] == `\\` { + return path + } + + if !_is_absolute_path(path) { // relative path + return path + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + PREFIX :: `\\?` + path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator) + copy(path_buf, PREFIX) + n := len(path) + r, w := 0, len(PREFIX) + for r < n { + switch { + case is_path_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + // \.\ + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + // Skip \..\ paths + return path + case: + path_buf[w] = '\\' + w += 1 + for r < n && !is_path_separator(path[r]) { + path_buf[w] = path[r] + r += 1 + w += 1 + } + } + } + + // Root directories require a trailing \ + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + + return string(path_buf[:w]) +} + +_are_paths_identical :: strings.equal_fold + +_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { + // Preserve rooted paths. + start = _volume_name_len(path) + if start > 0 { + rooted = true + if len(path) > start && _is_path_separator(path[start]) { + // Take `C:` to `C:\`. + start += 1 + } + copy(buffer, path[:start]) + for n in 0.. bool { + if _is_reserved_name(path) { + return true + } + if len(path) > 0 && _is_path_separator(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_path_separator(path[0]) +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator) + n := win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), 0, nil, nil) + if n == 0 { + return "", _get_platform_error() + } + + buf := make([]u16, n, temp_allocator) or_return + n = win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), u32(n), cstring16(raw_data(buf)), nil) + if n == 0 { + return "", _get_platform_error() + } + + return win32.utf16_to_utf8(buf, allocator) +} + +_get_relative_path_handle_start :: proc(base, target: string) -> bool { + base_root := base[:_volume_name_len(base)] + target_root := target[:_volume_name_len(target)] + return strings.equal_fold(base_root, target_root) +} + +_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 strings.equal_fold(base[i:j], target[i:j]) { + i = j + } else { + break + } + } + } + return i +} + +_split_path :: proc(path: string) -> (dir, file: string) { + vol_len := _volume_name_len(path) + + i := len(path) - 1 + for i >= vol_len && !_is_path_separator(path[i]) { + i -= 1 + } + if i == vol_len { + return path[:i+1], path[i+1:] + } else if i > vol_len { + return path[:i], path[i+1:] + } + return "", path +} \ No newline at end of file diff --git a/core/os/pipe.odin b/core/os/pipe.odin new file mode 100644 index 000000000..5d3e8368e --- /dev/null +++ b/core/os/pipe.odin @@ -0,0 +1,43 @@ +package os2 + +/* +Create an anonymous pipe. + +This procedure creates an anonymous pipe, returning two ends of the pipe, `r` +and `w`. The file `r` is the readable end of the pipe. The file `w` is a +writeable end of the pipe. + +Pipes are used as an inter-process communication mechanism, to communicate +between a parent and a child process. The child uses one end of the pipe to +write data, and the parent uses the other end to read from the pipe +(or vice-versa). When a parent passes one of the ends of the pipe to the child +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 +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. +*/ +@(require_results) +pipe :: proc() -> (r, w: ^File, err: Error) { + return _pipe() +} + +/* +Check if the pipe has any data. + +This procedure checks whether a read-end of the pipe has data that can be +read, and returns `true`, if the pipe has readable data, and `false` if the +pipe is empty. This procedure does not block the execution of the current +thread. + +**Note**: If the other end of the pipe was closed by the child process, the +`.Broken_Pipe` +can be returned by this procedure. Handle these errors accordingly. +*/ +@(require_results) +pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + return _pipe_has_data(r) +} diff --git a/core/os/pipe_js.odin b/core/os/pipe_js.odin new file mode 100644 index 000000000..253228f86 --- /dev/null +++ b/core/os/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/pipe_linux.odin b/core/os/pipe_linux.odin new file mode 100644 index 000000000..bb4456e1c --- /dev/null +++ b/core/os/pipe_linux.odin @@ -0,0 +1,43 @@ +#+private +package os2 + +import "core:sys/linux" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + fds: [2]linux.Fd + errno := linux.pipe2(&fds, {.CLOEXEC}) + if errno != .NONE { + return nil, nil,_get_platform_error(errno) + } + + r = _new_file(uintptr(fds[0]), "", file_allocator()) or_return + w = _new_file(uintptr(fds[1]), "", file_allocator()) or_return + + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := linux.Fd((^File_Impl)(r.impl).fd) + poll_fds := []linux.Poll_Fd { + linux.Poll_Fd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n, errno := linux.poll(poll_fds, 0) + if n != 1 || errno != nil { + return false, _get_platform_error(errno) + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} \ No newline at end of file diff --git a/core/os/pipe_posix.odin b/core/os/pipe_posix.odin new file mode 100644 index 000000000..7c07bc068 --- /dev/null +++ b/core/os/pipe_posix.odin @@ -0,0 +1,73 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" +import "core:strings" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + fds: [2]posix.FD + if posix.pipe(&fds) != .OK { + err = _get_platform_error() + return + } + + if posix.fcntl(fds[0], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + if posix.fcntl(fds[1], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + + r = __new_file(fds[0], file_allocator()) + ri := (^File_Impl)(r.impl) + + rname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&rname, "/dev/fd/") + strings.write_int(&rname, int(fds[0])) + ri.name = strings.to_string(rname) + ri.cname = strings.to_cstring(&rname) or_return + + w = __new_file(fds[1], file_allocator()) + wi := (^File_Impl)(w.impl) + + wname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&wname, "/dev/fd/") + strings.write_int(&wname, int(fds[1])) + wi.name = strings.to_string(wname) + wi.cname = strings.to_cstring(&wname) or_return + + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := __fd(r) + poll_fds := []posix.pollfd { + posix.pollfd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n := posix.poll(raw_data(poll_fds), u32(len(poll_fds)), 0) + if n < 0 { + return false, _get_platform_error() + } else if n != 1 { + return false, nil + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} diff --git a/core/os/pipe_wasi.odin b/core/os/pipe_wasi.odin new file mode 100644 index 000000000..19c11b51d --- /dev/null +++ b/core/os/pipe_wasi.odin @@ -0,0 +1,13 @@ +#+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/pipe_windows.odin b/core/os/pipe_windows.odin new file mode 100644 index 000000000..d6dc47c9c --- /dev/null +++ b/core/os/pipe_windows.odin @@ -0,0 +1,29 @@ +#+private +package os2 + +import win32 "core:sys/windows" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + p: [2]win32.HANDLE + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + } + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { + return nil, nil, _get_platform_error() + } + return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + handle := win32.HANDLE((^File_Impl)(r.impl).fd) + bytes_available: u32 + if !win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { + return false, _get_platform_error() + } + return bytes_available > 0, nil +} \ No newline at end of file diff --git a/core/os/process.odin b/core/os/process.odin new file mode 100644 index 000000000..e4fecf2a5 --- /dev/null +++ b/core/os/process.odin @@ -0,0 +1,548 @@ +package os2 + +import "base:runtime" + +import "core:time" + +/* +In procedures that explicitly state this as one of the allowed values, +specifies an infinite timeout. +*/ +TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity + +/* +Arguments to the current process. +*/ +args := get_args() + +@(private="file") +internal_args_to_free: []string + +@(private="file") +get_args :: proc "contextless" () -> []string { + context = runtime.default_context() + result := make([]string, len(runtime.args__), heap_allocator()) + for rt_arg, i in runtime.args__ { + result[i] = string(rt_arg) + } + internal_args_to_free = result + return result +} + +@(fini, private="file") +delete_args :: proc "contextless" () { + if internal_args_to_free != nil { + context = runtime.default_context() + delete(internal_args_to_free, heap_allocator()) + } +} + +/* +Exit the current process. +*/ +exit :: proc "contextless" (code: int) -> ! { + runtime.exit(code) +} + +/* +Obtain the UID of the current process. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. +*/ +@(require_results) +get_uid :: proc() -> int { + return _get_uid() +} + +/* +Obtain the effective UID of the current process. + +The effective UID is typically the same as the UID of the process. In case +the process was run by a user with elevated permissions, the process may +lower the privilege to perform some tasks without privilege. In these cases +the real UID of the process and the effective UID are different. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. +*/ +@(require_results) +get_euid :: proc() -> int { + return _get_euid() +} + +/* +Obtain the GID of the current process. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. +*/ +@(require_results) +get_gid :: proc() -> int { + return _get_gid() +} + +/* +Obtain the effective GID of the current process. + +The effective GID is typically the same as the GID of the process. In case +the process was run by a user with elevated permissions, the process may +lower the privilege to perform some tasks without privilege. In these cases +the real GID of the process and the effective GID are different. + +**Note(windows)**: Windows doesn't follow the posix permissions model, so +the function simply returns -1. +*/ +@(require_results) +get_egid :: proc() -> int { + return _get_egid() +} + +/* +Obtain the ID of the current process. +*/ +@(require_results) +get_pid :: proc() -> int { + return _get_pid() +} + +/* +Obtain the ID of the parent process. + +**Note(windows)**: Windows does not mantain strong relationships between +parent and child processes. This function returns the ID of the process +that has created the current process. In case the parent has died, the ID +returned by this function can identify a non-existent or a different +process. +*/ +@(require_results) +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. +*/ +@(require_results) +process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { + return _process_list(allocator) +} + +/* +Bit set specifying which fields of the `Process_Info` struct need to be +obtained by the `process_info()` procedure. Each bit corresponds to a +field in the `Process_Info` struct. +*/ +Process_Info_Fields :: bit_set[Process_Info_Field] +Process_Info_Field :: enum { + Executable_Path, + PPid, + Priority, + Command_Line, + Command_Args, + Environment, + Username, + Working_Dir, +} + +ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} + +/* +Contains information about the process as obtained by the `process_info()` +procedure. +*/ +Process_Info :: struct { + // The information about a process the struct contains. `pid` is always + // stored, no matter what. + fields: Process_Info_Fields, + // The ID of the process. + pid: int, + // The ID of the parent process. + ppid: int, + // The process priority. + priority: int, + // The path to the executable, which the process runs. + executable_path: string, + // The command line supplied to the process. + command_line: string, + // The arguments supplied to the process. + command_args: []string, + // The environment of the process. + environment: []string, + // The username of the user who started the process. + username: string, + // The current working directory of the process. + working_dir: string, +} + +/* +Obtain information about a process. + +This procedure obtains an information, specified by `selection` parameter of +a process given by `pid`. + +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(require_results) +process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_pid(pid, selection, allocator) +} + +/* +Obtain information about a process. + +This procedure obtains information, specified by `selection` parameter +about a process that has been opened by the application, specified in +the `process` parameter. + +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(require_results) +process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_handle(process, selection, allocator) +} + +/* +Obtain information about the current process. + +This procedure obtains the information, specified by `selection` parameter +about the currently running process. + +Use `free_process_info` to free the memory allocated by this procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(require_results) +current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _current_process_info(selection, allocator) +} + +/* +Obtain information about the specified process. +*/ +process_info :: proc { + process_info_by_pid, + process_info_by_handle, + current_process_info, +} + +/* +Free the information about the process. + +This procedure frees the memory occupied by process info using the provided +allocator. The allocator needs to be the same allocator that was supplied +to the `process_info` function. +*/ +free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { + delete(pi.executable_path, allocator) + delete(pi.command_line, allocator) + for a in pi.command_args { + delete(a, allocator) + } + delete(pi.command_args, allocator) + for s in pi.environment { + delete(s, allocator) + } + delete(pi.environment, allocator) + delete(pi.working_dir, allocator) + delete(pi.username, allocator) +} + +/* +Represents a process handle. + +When a process dies, the OS is free to re-use the pid of that process. The +`Process` struct represents a handle to the process that will refer to a +specific process, even after it has died. + +**Note(linux)**: The `handle` will be referring to pidfd. +*/ +Process :: struct { + pid: int, + handle: uintptr, +} + +Process_Open_Flags :: bit_set[Process_Open_Flag] +Process_Open_Flag :: enum { + // Request for reading from the virtual memory of another process. + Mem_Read, + // Request for writing to the virtual memory of another process. + Mem_Write, +} + +/* +Open a process handle using it's pid. + +This procedure obtains a process handle of a process specified by `pid`. +This procedure can be subject to race conditions. See the description of +`Process`. + +Use `process_close()` function to close the process handle. +*/ +@(require_results) +process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { + return _process_open(pid, flags) +} + +/* + The description of how a process should be created. +*/ +Process_Desc :: struct { + // The working directory of the process. If the string has length 0, the + // working directory is assumed to be the current working directory of the + // current process. + working_dir: string, + // The command to run. Each element of the slice is a separate argument to + // the process. The first element of the slice would be the executable. + command: []string, + // A slice of strings, each having the format `KEY=VALUE` representing the + // full environment that the child process will receive. + // In case this slice is `nil`, the current process' environment is used. + // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env. + env: []string, + // The `stderr` handle to give to the child process. It can be either a file + // or a writeable end of a pipe. Passing `nil` will shut down the process' + // stderr output. + stderr: ^File, + // The `stdout` handle to give to the child process. It can be either a file + // or a writeabe end of a pipe. Passing a `nil` will shut down the process' + // stdout output. + stdout: ^File, + // The `stdin` handle to give to the child process. It can either be a file + // or a readable end of a pipe. Passing a `nil` will shut down the process' + // input. + stdin: ^File, +} + +/* +Create a new process and obtain its handle. + +This procedure creates a new process, with a given command and environment +strings as parameters. Use `environ()` to inherit the environment of the +current process. + +The `desc` parameter specifies the description of how the process should +be created. It contains information such as the command line, the +environment of the process, the starting directory and many other options. +Most of the fields in the struct can be set to `nil` or an empty value. + +Use `process_close` to close the handle to the process. Note, that this +is not the same as terminating the process. One can terminate the process +and not close the handle, in which case the handle would be leaked. In case +the function returns an error, an invalid handle is returned. + +This procedure is not thread-safe. It may alter the inheritance properties +of file handles in an unpredictable manner. In case multiple threads change +handle inheritance properties, make sure to serialize all those calls. +*/ +@(require_results) +process_start :: proc(desc: Process_Desc) -> (Process, Error) { + return _process_start(desc) +} + +/* +Execute the process and capture stdout and stderr streams. + +This procedure creates a new process, with a given command and environment +strings as parameters, and waits until the process finishes execution. While +the process is running, this procedure accumulates the output of its stdout +and stderr streams and returns byte slices containing the captured data from +the streams. + +This procedure expects that `stdout` and `stderr` fields of the `desc` parameter +are left at default, i.e. a `nil` value. You can not capture stdout/stderr and +redirect it to a file at the same time. + +This procedure does not free `stdout` and `stderr` slices before an error is +returned. Make sure to call `delete` on these slices. +*/ +@(require_results) +process_exec :: proc( + desc: Process_Desc, + allocator: runtime.Allocator, + loc := #caller_location, +) -> ( + state: Process_State, + stdout: []byte, + stderr: []byte, + err: Error, +) { + assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc) + assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc) + + stdout_r, stdout_w := pipe() or_return + defer close(stdout_r) + stderr_r, stderr_w := pipe() or_return + defer close(stderr_r) + + process: Process + { + // NOTE(flysand): Make sure the write-ends are closed, regardless + // of the outcome. This makes read-ends readable on our side. + defer close(stdout_w) + defer close(stderr_w) + desc := desc + desc.stdout = stdout_w + desc.stderr = stderr_w + process = process_start(desc) or_return + } + + { + stdout_b: [dynamic]byte + stdout_b.allocator = allocator + + stderr_b: [dynamic]byte + stderr_b.allocator = allocator + + buf: [1024]u8 = --- + + stdout_done, stderr_done, has_data: bool + for err == nil && (!stdout_done || !stderr_done) { + n := 0 + + if !stdout_done { + has_data, err = pipe_has_data(stdout_r) + if has_data { + n, err = read(stdout_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stdout_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stdout_done = true + err = nil + } + } + + if err == nil && !stderr_done { + n = 0 + has_data, err = pipe_has_data(stderr_r) + if has_data { + n, err = read(stderr_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stderr_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stderr_done = true + err = nil + } + } + } + + stdout = stdout_b[:] + stderr = stderr_b[:] + } + + if err != nil { + state, _ = process_wait(process, timeout=0) + if !state.exited { + _ = process_kill(process) + state, _ = process_wait(process) + } + return + } + + state, err = process_wait(process) + return +} + +/* + The state of the process after it has finished execution. +*/ +Process_State :: struct { + // The ID of the process. + pid: int, + // Specifies whether the process has terminated or is still running. + exited: bool, + // The exit code of the process, if it has exited. + // Will also store the number of the exception or signal that has crashed the + // process. + exit_code: int, + // Specifies whether the termination of the process was successfull or not, + // i.e. whether it has crashed or not. + // **Note(windows)**: On windows `true` is always returned, as there is no + // reliable way to obtain information about whether the process has crashed. + success: bool, + // The time the process has spend executing in kernel time. + system_time: time.Duration, + // The time the process has spend executing in userspace. + user_time: time.Duration, +} + +/* +Wait for a process event. + +This procedure blocks the execution until the process has exited or the +timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, +no timeout restriction is imposed and the procedure can block indefinately. + +If the timeout has expired, the `General_Error.Timeout` is returned as +the error. + +If an error is returned for any other reason, other than timeout, the +process state is considered undetermined. +*/ +@(require_results) +process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { + return _process_wait(process, timeout) +} + +/* +Close the handle to a process. + +This procedure closes the handle associated with a process. It **does not** +terminate a process, in case it was running. In case a termination is +desired, kill the process first, wait for the process to finish, +then close the handle. +*/ +@(require_results) +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) +} + +/* +Terminate a process. + +This procedure terminates a process, specified by it's handle, `process`. +*/ +@(require_results) +process_kill :: proc(process: Process) -> (Error) { + return _process_kill(process) +} diff --git a/core/os/process_freebsd.odin b/core/os/process_freebsd.odin new file mode 100644 index 000000000..8a31eb62c --- /dev/null +++ b/core/os/process_freebsd.odin @@ -0,0 +1,36 @@ +#+private +#+build freebsd +package os2 + +import "core:c" + +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/process_js.odin b/core/os/process_js.odin new file mode 100644 index 000000000..a59a79d45 --- /dev/null +++ b/core/os/process_js.odin @@ -0,0 +1,95 @@ +#+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 +} + +_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 +} + +_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/process_linux.odin b/core/os/process_linux.odin new file mode 100644 index 000000000..4afd9f3fc --- /dev/null +++ b/core/os/process_linux.odin @@ -0,0 +1,868 @@ +#+build linux +#+private file +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") +_get_uid :: proc() -> int { + return int(linux.getuid()) +} + +@(private="package") +_get_euid :: proc() -> int { + return int(linux.geteuid()) +} + +@(private="package") +_get_gid :: proc() -> int { + return int(linux.getgid()) +} + +@(private="package") +_get_egid :: proc() -> int { + return int(linux.getegid()) +} + +@(private="package") +_get_pid :: proc() -> int { + return int(linux.getpid()) +} + +@(private="package") +_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 }) + + dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + // okay + case .ENOTDIR: + err = .Invalid_Dir + return + case .ENOENT: + err = .Not_Exist + return + case: + err = _get_platform_error(errno) + return + } + defer linux.close(dir_fd) + + dynamic_list := make([dynamic]int, temp_allocator) or_return + + buf := make([dynamic]u8, 128, 128, temp_allocator) or_return + loop: for { + buflen: int + buflen, errno = linux.getdents(dir_fd, buf[:]) + #partial switch errno { + case .EINVAL: + resize(&buf, len(buf) * 2) + continue loop + case .NONE: + if buflen == 0 { break loop } + case: + return {}, _get_platform_error(errno) + } + + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + + if pid, ok := strconv.parse_int(d_name_str); ok { + append(&dynamic_list, pid) + } + } + } + + list, err = slice.clone(dynamic_list[:], allocator) + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + info.pid = pid + + // Use this to make cstrings without copying. + path_backing: [48]u8 + path_builder := strings.builder_from_bytes(path_backing[:]) + + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + proc_fd, errno := linux.open(strings.to_cstring(&path_builder) or_return, _OPENDIR_FLAGS) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + defer linux.close(proc_fd) + + username_if: if .Username in selection { + s: linux.Stat + if errno = linux.fstat(proc_fd, &s); errno != .NONE { + err = _get_platform_error(errno) + break username_if + } + + passwd_bytes: []u8 + passwd_err: Error + passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator) + if passwd_err != nil { + err = passwd_err + break username_if + } + + passwd := string(passwd_bytes) + for len(passwd) > 0 { + n := strings.index_byte(passwd, ':') + if n < 0 { + break + } + username := passwd[:n] + passwd = passwd[n+1:] + + // skip password field + passwd = passwd[strings.index_byte(passwd, ':') + 1:] + + n = strings.index_byte(passwd, ':') + if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) { + info.username = strings.clone(username, allocator) or_return + info.fields += {.Username} + break + } else if !ok { + err = .Invalid_File + break username_if + } + + eol := strings.index_byte(passwd, '\n') + if eol < 0 { + break + } + passwd = passwd[eol + 1:] + } + } + + cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cmdline") + + cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) + if cmdline_err != nil || len(cmdline_bytes) == 0 { + err = cmdline_err + break cmdline_if + } + cmdline := string(cmdline_bytes) + + terminator := strings.index_byte(cmdline, 0) + assert(terminator > 0) + + // command_line_exec := cmdline[:terminator] + + // Still need cwd if the execution on the command line is relative. + cwd: string + cwd_err: Error + if .Working_Dir in selection { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cwd") + + cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail + if cwd_err == nil && .Working_Dir in selection { + info.working_dir = strings.clone(cwd, allocator) or_return + info.fields += {.Working_Dir} + } else if cwd_err != nil { + err = cwd_err + break cmdline_if + } + } + + if selection & {.Command_Line, .Command_Args} != {} { + // skip to first arg + //cmdline = cmdline[terminator + 1:] + command_line_builder: strings.Builder + command_args_list: [dynamic]string + + if .Command_Line in selection { + command_line_builder = strings.builder_make(allocator) or_return + info.fields += {.Command_Line} + } + + for i := 0; len(cmdline) > 0; i += 1 { + if terminator = strings.index_byte(cmdline, 0); terminator < 0 { + break + } + + if .Command_Line in selection { + if i > 0 { + strings.write_byte(&command_line_builder, ' ') + } + strings.write_string(&command_line_builder, cmdline[:terminator]) + } + if .Command_Args in selection { + if i == 1 { + command_args_list = make([dynamic]string, allocator) or_return + info.fields += {.Command_Args} + } + if i > 0 { + arg := strings.clone(cmdline[:terminator], allocator) or_return + append(&command_args_list, arg) or_return + } + } + + cmdline = cmdline[terminator + 1:] + } + info.command_line = strings.to_string(command_line_builder) + info.command_args = command_args_list[:] + } + } + + stat_if: if selection & {.PPid, .Priority} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/stat") + + proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) + if stat_err != nil { + err = stat_err + break stat_if + } + if len(proc_stat_bytes) <= 0 { + break stat_if + } + + // Skip to the first field after the executable name + stats: string + if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 { + stats = string(proc_stat_bytes[start + 2:]) + } else { + break stat_if + } + + // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat` + // because we skipped passed the executable name above. + Fields :: enum { + State, + PPid, + PGrp, + Session, + Tty_Nr, + TpGid, + Flags, + MinFlt, + CMinFlt, + MajFlt, + CMajFlt, + UTime, + STime, + CUTime, + CSTime, + Priority, + Nice, + //... etc, + } + stat_fields := strings.split(stats, " ", temp_allocator) or_return + + if len(stat_fields) <= int(Fields.Nice) { + break stat_if + } + + if .PPid in selection { + if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok { + info.ppid = ppid + info.fields += {.PPid} + } else { + err = .Invalid_File + break stat_if + } + } + + if .Priority in selection { + if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok { + info.priority = nice + info.fields += {.Priority} + } else { + err = .Invalid_File + break stat_if + } + } + } + + if .Executable_Path in selection { + /* + NOTE(Jeroen): + + The old version returned the wrong executable path for things like `bash` or `sh`, + for whom `/proc//cmdline` will just report "bash" or "sh", + resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there. + + Thanks to Yawning for suggesting `/proc/self/exe`. + */ + + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/exe") + + if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil { + info.executable_path = strings.clone(string(exe_bytes), allocator) or_return + info.fields += {.Executable_Path} + } else { + err = exe_err + } + } + + if .Environment in selection { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/environ") + + if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil { + env := string(env_bytes) + + env_list := make([dynamic]string, allocator) or_return + for len(env) > 0 { + terminator := strings.index_byte(env, 0) + if terminator <= 0 { + break + } + e := strings.clone(env[:terminator], allocator) or_return + append(&env_list, e) or_return + env = env[terminator + 1:] + } + info.environment = env_list[:] + info.fields += {.Environment} + } else if err == nil { + err = env_err + } + } + + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(get_pid(), selection, allocator) +} + +@(private="package") +_process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + process.handle = PIDFD_UNASSIGNED + + pidfd, errno := linux.pidfd_open(linux.Pid(pid), {}) + if errno == .ENOSYS { + return process, .Unsupported + } + if errno != .NONE { + return process, _get_platform_error(errno) + } + process.handle = uintptr(pidfd) + return +} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + if len(desc.command) == 0 { + return process, .Invalid_Command + } + + dir_fd := linux.AT_FDCWD + errno: linux.Errno + if desc.working_dir != "" { + dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return + if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { + return process, _get_platform_error(errno) + } + } + defer if desc.working_dir != "" { + linux.close(dir_fd) + } + + // search PATH if just a plain name is provided + exe_path: cstring + executable_name := desc.command[0] + if strings.index_byte(executable_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return + + exe_builder := strings.builder_make(temp_allocator) or_return + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, executable_name) + + exe_path = strings.to_cstring(&exe_builder) or_return + stat := linux.Stat{} + if linux.stat(exe_path, &stat) == .NONE && .IFREG in stat.mode && .IXUSR in stat.mode { + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, executable_name) + + exe_path = strings.to_cstring(&exe_builder) or_return + if linux.access(exe_path, linux.X_OK) != .NONE { + return process, .Not_Exist + } + } + } else { + exe_path = clone_to_cstring(executable_name, temp_allocator) or_return + if linux.access(exe_path, linux.X_OK) != .NONE { + return process, .Not_Exist + } + } + + // args and environment need to be a list of cstrings + // that are terminated by a nil pointer. + cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return + for command, i in desc.command { + cargs[i] = clone_to_cstring(command, temp_allocator) or_return + } + + // Use current process' environment if description didn't provide it. + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = raw_data(export_cstring_environment(temp_allocator)) + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return + for env, i in desc.env { + cenv[i] = clone_to_cstring(env, temp_allocator) or_return + } + env = &cenv[0] + } + + child_pipe_fds: [2]linux.Fd + if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE { + return process, _get_platform_error(errno) + } + defer linux.close(child_pipe_fds[READ]) + + // TODO: This is the traditional textbook implementation with fork. + // A more efficient implementation with vfork: + // + // 1. retrieve signal handlers + // 2. block all signals + // 3. allocate some stack space + // 4. vfork (waits for child exit or execve); In child: + // a. set child signal handlers + // b. set up any necessary pipes + // c. execve + // 5. restore signal handlers + // + pid: linux.Pid + if pid, errno = linux.fork(); errno != .NONE { + linux.close(child_pipe_fds[WRITE]) + return process, _get_platform_error(errno) + } + + STDIN :: linux.Fd(0) + STDOUT :: linux.Fd(1) + STDERR :: linux.Fd(2) + + READ :: 0 + WRITE :: 1 + + if pid == 0 { + // in child process now + write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! { + error_byte: [1]u8 = { u8(errno) } + linux.write(parent_fd, error_byte[:]) + linux.exit(126) + } + + stdin_fd: linux.Fd + stdout_fd: linux.Fd + stderr_fd: linux.Fd + + if desc.stdin != nil { + stdin_fd = linux.Fd(fd(desc.stdin)) + } else { + stdin_fd, errno = linux.open("/dev/null", {}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + + write_devnull: linux.Fd = -1 + + if desc.stdout != nil { + stdout_fd = linux.Fd(fd(desc.stdout)) + } else { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + stdout_fd = write_devnull + } + + if desc.stderr != nil { + stderr_fd = linux.Fd(fd(desc.stderr)) + } else { + if write_devnull < 0 { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + stderr_fd = write_devnull + } + + if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if dir_fd != linux.AT_FDCWD { + if errno = linux.fchdir(dir_fd); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + + errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) + assert(errno != nil) + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + + linux.close(child_pipe_fds[WRITE]) + + process.pid = int(pid) + + child_byte: [1]u8 + errno = .EINTR + for errno == .EINTR { + _, errno = linux.read(child_pipe_fds[READ], child_byte[:]) + } + + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + if errno == .NONE { + child_errno := linux.Errno(child_byte[0]) + if child_errno != .NONE { + // We can assume it trapped here. + _reap_terminated(process) + process.pid = 0 + return process, _get_platform_error(child_errno) + } + } + + process, _ = process_open(int(pid)) + return +} + +_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + stat_path_buf: [48]u8 + path_builder := strings.builder_from_bytes(stat_path_buf[:]) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, int(state.pid)) + strings.write_string(&path_builder, "/stat") + + stat_buf: []u8 + stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) + if err != nil { + return + } + + // ')' will be the end of the executable name (item 2) + idx := strings.last_index_byte(string(stat_buf), ')') + stats := string(stat_buf[idx + 2:]) + + // utime and stime are the 14 and 15th items, respectively, and we are + // currently on item 3. Skip 11 items here. + for _ in 0..<11 { + stats = stats[strings.index_byte(stats, ' ') + 1:] + } + + idx = strings.index_byte(stats, ' ') + utime_str := stats[:idx] + + stats = stats[idx + 1:] + stime_str := stats[:strings.index_byte(stats, ' ')] + + utime, stime: int + ok: bool + if utime, ok = strconv.parse_int(utime_str, 10); !ok { + return .Invalid_File + } + if stime, ok = strconv.parse_int(stime_str, 10); !ok { + return .Invalid_File + } + + // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms + state.user_time = time.Duration(utime) * 10 * time.Millisecond + state.system_time = time.Duration(stime) * 10 * time.Millisecond + + return +} + +_reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) { + state.pid = process.pid + _process_state_update_times(&state) + + info: linux.Sig_Info + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + state.exited = true + state.exit_code = int(info.status) + state.success = state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + state.exited = true + state.exit_code = int(info.status) + state.success = false + } + return +} + +_timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + + process_state.pid = process.pid + pidfd := linux.Fd(process.handle) + pollfd: [1]linux.Poll_Fd = { + { + fd = pidfd, + events = {.IN}, + }, + } + + start_tick := time.tick_now() + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + info: linux.Sig_Info + for { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set) + if errno != .NONE { + if errno == .EINTR { + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + continue + } + return process_state, _get_platform_error(errno) + } + + if n == 0 { // timeout with no events + _process_state_update_times(&process_state) + err = .Timeout + return + } + + if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE { + return process_state, _get_platform_error(errno) + } + + if info.signo == .SIGCHLD { + break + } + + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + } + + // _reap_terminated for pidfd + { + _process_state_update_times(&process_state) + + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = false + } + } + return +} + +_timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + process_state.pid = process.pid + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + start_tick := time.tick_now() + + org_sigset: linux.Sig_Set + errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset) + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) + + // In case there was a signal handler on SIGCHLD, avoid race + // condition by checking wait first. + info: linux.Sig_Info + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil) + + for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts) + #partial switch errno { + case .EAGAIN: // timeout + _process_state_update_times(&process_state) + err = .Timeout + return + case .EINTR: + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + case .EINVAL: + return process_state, _get_platform_error(errno) + } + } + + return _reap_terminated(process) +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { + if timeout > 0 { + if process.handle == PIDFD_UNASSIGNED { + return _timed_wait_on_pid(process, timeout) + } else { + return _timed_wait_on_handle(process, timeout) + } + } + + process_state: Process_State = { + pid = process.pid, + } + + errno: linux.Errno + options: linux.Wait_Options = {.WEXITED} + if timeout == 0 { + options += {.WNOHANG} + } + + info: linux.Sig_Info + + errno = .EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil) + } + if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) { + _process_state_update_times(&process_state) + return process_state, .Timeout + } + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + + return _reap_terminated(process) +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if process.handle == 0 || process.handle == PIDFD_UNASSIGNED { + return nil + } + pidfd := linux.Fd(process.handle) + return _get_platform_error(linux.close(pidfd)) +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL)) +} + diff --git a/core/os/process_netbsd.odin b/core/os/process_netbsd.odin new file mode 100644 index 000000000..b46a58e58 --- /dev/null +++ b/core/os/process_netbsd.odin @@ -0,0 +1,31 @@ +#+private +#+build netbsd +package os2 + +import "core:c" +foreign import libc "system:c" + +@(private) +foreign libc { + _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/process_openbsd.odin b/core/os/process_openbsd.odin new file mode 100644 index 000000000..9c6605952 --- /dev/null +++ b/core/os/process_openbsd.odin @@ -0,0 +1,25 @@ +#+private +#+build openbsd +package os2 + +import "core:c" + +foreign import libc "system:c" + +@(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/process_posix.odin b/core/os/process_posix.odin new file mode 100644 index 000000000..a48e44900 --- /dev/null +++ b/core/os/process_posix.odin @@ -0,0 +1,344 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:time" +import "core:strings" + +import kq "core:sys/kqueue" +import "core:sys/posix" + +_get_uid :: proc() -> int { + return int(posix.getuid()) +} + +_get_euid :: proc() -> int { + return int(posix.geteuid()) +} + +_get_gid :: proc() -> int { + return int(posix.getgid()) +} + +_get_egid :: proc() -> int { + return int(posix.getegid()) +} + +_get_pid :: proc() -> int { + return int(posix.getpid()) +} + +_get_ppid :: proc() -> int { + return int(posix.getppid()) +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(_get_pid(), selection, allocator) +} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + if len(desc.command) == 0 { + err = .Invalid_Path + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + // search PATH if just a plain name is provided. + exe_builder := strings.builder_make(temp_allocator) + exe_name := desc.command[0] + if strings.index_byte(exe_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { + continue + } else { + posix.close(exe_fd) + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, desc.working_dir) + if len(desc.working_dir) > 0 && desc.working_dir[len(desc.working_dir)-1] != '/' { + strings.write_byte(&exe_builder, '/') + } + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, exe_name) + + // "hello/./world" is fine right? + + if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + } else { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder) or_return, {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + + cwd: cstring; if desc.working_dir != "" { + cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return + } + + cmd := make([]cstring, len(desc.command) + 1, temp_allocator) + for part, i in desc.command { + cmd[i] = clone_to_cstring(part, temp_allocator) or_return + } + + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = posix.environ + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) + for env, i in desc.env { + cenv[i] = clone_to_cstring(env, temp_allocator) or_return + } + env = raw_data(cenv) + } + + READ :: 0 + WRITE :: 1 + + pipe: [2]posix.FD + if posix.pipe(&pipe) != .OK { + err = _get_platform_error() + return + } + defer posix.close(pipe[READ]) + + if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + + switch pid := posix.fork(); pid { + case -1: + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + + case 0: + abort :: proc(parent_fd: posix.FD) -> ! { + #assert(len(posix.Errno) < max(u8)) + errno := u8(posix.errno()) + posix.write(parent_fd, &errno, 1) + posix.exit(126) + } + + null := posix.open("/dev/null", {.RDWR}) + if null == -1 { abort(pipe[WRITE]) } + + stderr := (^File_Impl)(desc.stderr.impl).fd if desc.stderr != nil else null + stdout := (^File_Impl)(desc.stdout.impl).fd if desc.stdout != nil else null + stdin := (^File_Impl)(desc.stdin.impl).fd if desc.stdin != nil else null + + if posix.dup2(stderr, posix.STDERR_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdout, posix.STDOUT_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdin, posix.STDIN_FILENO ) == -1 { abort(pipe[WRITE]) } + + if cwd != nil { + if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } + } + + res := posix.execve(strings.to_cstring(&exe_builder) or_return, raw_data(cmd), env) + assert(res == -1) + abort(pipe[WRITE]) + + case: + posix.close(pipe[WRITE]) + + errno: posix.Errno + for { + errno_byte: u8 + switch posix.read(pipe[READ], &errno_byte, 1) { + case 1: + errno = posix.Errno(errno_byte) + case -1: + errno = posix.errno() + if errno == .EINTR { + continue + } else { + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + errno = nil + } + } + break + } + + if errno != nil { + // We can assume it trapped here. + + for { + info: posix.siginfo_t + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 && posix.errno() == .EINTR { + continue + } + break + } + + err = errno + return + } + + process, _ = _process_open(int(pid), {}) + return + } +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + process_state.pid = process.pid + + _process_handle_still_valid(process) or_return + + // timeout > 0 = use kqueue to wait (with a timeout) on process exit + // timeout == 0 = use waitid with WNOHANG so it returns immediately + // timeout > 0 = use waitid without WNOHANG so it waits indefinitely + // + // at the end use waitid to actually reap the process and get it's status + + if timeout > 0 { + timeout := timeout + + queue := kq.kqueue() or_return + defer posix.close(queue) + + changelist, eventlist: [1]kq.KEvent + + changelist[0] = { + ident = uintptr(process.pid), + filter = .Proc, + flags = { .Add }, + fflags = { + fproc = { .Exit }, + }, + } + + for { + start := time.tick_now() + n, kerr := kq.kevent(queue, changelist[:], eventlist[:], &{ + tv_sec = posix.time_t(timeout / time.Second), + tv_nsec = i64(timeout % time.Second), + }) + if kerr == .EINTR { + timeout -= time.tick_since(start) + continue + } else if kerr != nil { + err = kerr + return + } else if n == 0 { + err = .Timeout + _process_state_update_times(process, &process_state) + return + } else { + _process_state_update_times(process, &process_state) + break + } + } + } else { + flags := posix.Wait_Flags{.EXITED, .NOWAIT} + if timeout == 0 { + flags += {.NOHANG} + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, flags) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + _process_state_update_times(process, &process_state) + + if info.si_signo == nil { + assert(timeout == 0) + err = .Timeout + return + } + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + switch info.si_code.chld { + case: unreachable() + case .CONTINUED, .STOPPED: unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = false + } + + return +} + +_process_close :: proc(process: Process) -> Error { + return nil +} + +_process_kill :: proc(process: Process) -> (err: Error) { + _process_handle_still_valid(process) or_return + + if posix.kill(posix.pid_t(process.pid), .SIGKILL) != .OK { + err = _get_platform_error() + } + + return +} diff --git a/core/os/process_posix_darwin.odin b/core/os/process_posix_darwin.odin new file mode 100644 index 000000000..934d23711 --- /dev/null +++ b/core/os/process_posix_darwin.odin @@ -0,0 +1,332 @@ +#+private +package os2 + +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 pthread "system:System" + +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) +} + +_get_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) { + get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) { + // Short info is enough and requires less permissions if the priority isn't requested. + if .Priority in selection { + info: darwin.proc_taskallinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info)) + if ret > 0 { + assert(ret == size_of(info)) + ppid = info.pbsd.pbi_ppid + prio = info.ptinfo.pti_priority + uid = info.pbsd.pbi_uid + ok = true + return + } + } + + // Try short info, requires less permissions, but doesn't give a `nice`. + psinfo: darwin.proc_bsdshortinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo)) + if ret > 0 { + assert(ret == size_of(psinfo)) + ppid = psinfo.pbsi_ppid + uid = psinfo.pbsi_uid + ok = true + } + + return + } + + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + info.pid = pid + + // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), + // other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information. + + pidinfo: { + if selection & {.PPid, .Priority, .Username } != {} { + ppid, mprio, uid, ok := get_pidinfo(pid, selection) + if !ok { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + if .PPid in selection { + info.ppid = int(ppid) + info.fields += {.PPid} + } + + if prio, has_prio := mprio.?; has_prio && .Priority in selection { + info.priority = int(prio) + info.fields += {.Priority} + } + + if .Username in selection { + pw := posix.getpwuid(uid) + if pw == nil { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + info.username = clone_string(string(pw.pw_name), allocator) or_return + info.fields += {.Username} + } + } + } + + if .Working_Dir in selection { + pinfo: darwin.proc_vnodepathinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo)) + if ret > 0 { + assert(ret == size_of(pinfo)) + info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return + info.fields += {.Working_Dir} + } else if err == nil { + err = _get_platform_error() + } + } + + if .Executable_Path in selection { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return + info.fields += {.Executable_Path} + } else if err == nil { + err = _get_platform_error() + } + } + + args: if selection & { .Command_Line, .Command_Args, .Environment } != {} { + mib := []i32{ + unix.CTL_KERN, + unix.KERN_PROCARGS2, + i32(pid), + } + length: uint + if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + } + break args + } + + buf := runtime.make_aligned([]byte, length, 4, temp_allocator) + if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + + // Looks like EINVAL is returned here if you don't have permission. + if err == Platform_Error(posix.Errno.EINVAL) { + err = .Permission_Denied + } + } + break args + } + + buf = buf[:length] + + if len(buf) < 4 { + break args + } + + // Layout isn't really documented anywhere, I deduced it to be: + // i32 - argc + // cstring - command name (skipped) + // [^]byte - couple of 0 bytes (skipped) + // [^]cstring - argv (up to argc entries) + // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too) + + argc := (^i32)(raw_data(buf))^ + buf = buf[size_of(i32):] + + { + command_line: [dynamic]byte + command_line.allocator = allocator + + argv: [dynamic]string + argv.allocator = allocator + + defer if err != nil { + for arg in argv { delete(arg, allocator) } + delete(argv) + delete(command_line) + } + + _, _ = bytes.split_iterator(&buf, {0}) + buf = bytes.trim_left(buf, {0}) + + first_arg := true + for arg in bytes.split_iterator(&buf, {0}) { + if .Command_Line in selection { + if !first_arg { + append(&command_line, ' ') or_return + } + append(&command_line, ..arg) or_return + } + + if .Command_Args in selection { + sarg := clone_string(string(arg), allocator) or_return + append(&argv, sarg) or_return + } + + first_arg = false + argc -= 1 + if argc == 0 { + break + } + } + + if .Command_Line in selection { + info.command_line = string(command_line[:]) + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = argv[:] + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + environment: [dynamic]string + environment.allocator = allocator + + defer if err != nil { + for entry in environment { delete(entry, allocator) } + delete(environment) + } + + for entry in bytes.split_iterator(&buf, {0}) { + if bytes.index_byte(entry, '=') > -1 { + sentry := clone_string(string(entry), allocator) or_return + append(&environment, sentry) or_return + } + } + + info.environment = environment[:] + info.fields += {.Environment} + } + } + + // Fields were requested that we didn't add. + if err == nil && selection - info.fields != {} { + err = .Unsupported + } + + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + ret := darwin.proc_listallpids(nil, 0) + if ret < 0 { + err = _get_platform_error() + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + buffer := make([]i32, ret, temp_allocator) + ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) + if ret < 0 { + err = _get_platform_error() + return + } + + list = make([]int, ret, allocator) or_return + #no_bounds_check for &entry, i in list { + entry = int(buffer[i]) + } + + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 { + err = _get_platform_error() + return + } + + // Using the start time as the handle, there is no pidfd or anything on Darwin. + // There is a uuid, but once a process becomes a zombie it changes... + process.handle = uintptr(rusage.ri_proc_start_abstime) + process.pid = int(pid) + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return _get_platform_error() + } + + handle := uintptr(rusage.ri_proc_start_abstime) + if p.handle != handle { + return posix.Errno.ESRCH + } + + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return + } + + // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output. + HZ :: 20000000 + + state.user_time = ( + (time.Duration(rusage.ri_user_time) / HZ * time.Second) + + time.Duration(rusage.ri_user_time % HZ)) + state.system_time = ( + (time.Duration(rusage.ri_system_time) / HZ * time.Second) + + time.Duration(rusage.ri_system_time % HZ)) + + return +} diff --git a/core/os/process_posix_other.odin b/core/os/process_posix_other.odin new file mode 100644 index 000000000..65da3e9e2 --- /dev/null +++ b/core/os/process_posix_other.odin @@ -0,0 +1,29 @@ +#+private +#+build netbsd, openbsd, freebsd +package os2 + +import "base:runtime" + +_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/process_wasi.odin b/core/os/process_wasi.odin new file mode 100644 index 000000000..efb2c0228 --- /dev/null +++ b/core/os/process_wasi.odin @@ -0,0 +1,91 @@ +#+private +package os2 + +import "base:runtime" + +import "core:time" +// import "core:sys/wasm/wasi" + +_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 +} + +_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 +} + +_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/process_windows.odin b/core/os/process_windows.odin new file mode 100644 index 000000000..b2c87c4f4 --- /dev/null +++ b/core/os/process_windows.odin @@ -0,0 +1,799 @@ +#+private file +package os2 + +import "base:intrinsics" +import "base:runtime" + +import "core:strings" +import win32 "core:sys/windows" +import "core:time" + +@(private="package") +_get_uid :: proc() -> int { + return -1 +} + +@(private="package") +_get_euid :: proc() -> int { + return -1 +} + +@(private="package") +_get_gid :: proc() -> int { + return -1 +} + +@(private="package") +_get_egid :: proc() -> int { + return -1 +} + +@(private="package") +_get_pid :: proc() -> int { + return int(win32.GetCurrentProcessId()) +} + +@(private="package") +_get_ppid :: proc() -> int { + our_pid := win32.GetCurrentProcessId() + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + return -1 + } + defer win32.CloseHandle(snap) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + for status := win32.Process32FirstW(snap, &entry); status; /**/ { + if entry.th32ProcessID == our_pid { + return int(entry.th32ParentProcessID) + } + status = win32.Process32NextW(snap, &entry) + } + 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) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + + list_d := make([dynamic]int, allocator) or_return + + entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + append(&list_d, int(entry.th32ProcessID)) + status = win32.Process32NextW(snap, &entry) + } + list = list_d[:] + return +} + +@(require_results) +read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} +@(require_results) +read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = pid + // Note(flysand): Open the process handle right away to prevent some race + // conditions. Once the handle is open, the process will be kept alive by + // the OS. + ph := win32.INVALID_HANDLE_VALUE + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { + ph = win32.OpenProcess( + win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, + false, + u32(pid), + ) + if ph == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + } + defer if ph != win32.INVALID_HANDLE_VALUE { + win32.CloseHandle(ph) + } + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + snapshot_modules: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_modules + } + info.executable_path = exe_path + info.fields += {.Executable_Path} + } + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + break read_peb + } + assert(process_info.PebBaseAddress != nil) + process_peb: win32.PEB + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + if selection >= {.Command_Line, .Command_Args} { + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + temp_allocator_scope(temp_allocator) + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + pid := process.pid + info.pid = pid + // Data obtained from process snapshots + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + snapshot_module: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_module + } + info.executable_path = exe_path + info.fields += {.Executable_Path} + } + ph := win32.HANDLE(process.handle) + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + assert(process_info.PebBaseAddress != nil) + process_peb: win32.PEB + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + if selection >= {.Command_Line, .Command_Args} { + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + temp_allocator_scope(temp_allocator) + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = get_pid() + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + module_filename: if .Executable_Path in selection { + exe_filename_w: [256]u16 + path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + assert(path_len > 0) + info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.fields += {.Executable_Path} + } + command_line: if selection >= {.Command_Line, .Command_Args} { + command_line_w := win32.GetCommandLineW() + assert(command_line_w != nil) + if .Command_Line in selection { + info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(command_line_w, allocator) or_return + info.fields += {.Command_Args} + } + } + read_environment: if .Environment in selection { + env_block := win32.GetEnvironmentStringsW() + assert(env_block != nil) + info.environment = _parse_environment_block(env_block, allocator) or_return + info.fields += {.Environment} + } + read_username: if .Username in selection { + process_handle := win32.GetCurrentProcess() + username: string + username, err = _get_process_user(process_handle, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + if .Working_Dir in selection { + // TODO(flysand): Implement this by reading PEB + err = .Mode_Not_Implemented + return + } + err = nil + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + // Note(flysand): The handle will be used for querying information so we + // take the necessary permissions right away. + dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE + if .Mem_Read in flags { + dwDesiredAccess |= win32.PROCESS_VM_READ + } + if .Mem_Write in flags { + dwDesiredAccess |= win32.PROCESS_VM_WRITE + } + handle := win32.OpenProcess( + dwDesiredAccess, + false, + u32(pid), + ) + if handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + } else { + process = {pid = pid, handle = uintptr(handle)} + } + return +} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + command_line := _build_command_line(desc.command, temp_allocator) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return + environment := desc.env + if desc.env == nil { + environment = environ(temp_allocator) or_return + } + environment_block := _build_environment_block(environment, temp_allocator) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return + + stderr_handle: win32.HANDLE + stdout_handle: win32.HANDLE + stdin_handle: win32.HANDLE + + null_handle: win32.HANDLE + if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil { + null_handle = win32.CreateFileW( + win32.L("NUL"), + win32.GENERIC_READ|win32.GENERIC_WRITE, + win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE, + &win32.SECURITY_ATTRIBUTES{ + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + }, + win32.OPEN_EXISTING, + win32.FILE_ATTRIBUTE_NORMAL, + nil, + ) + // Opening NUL should always succeed. + assert(null_handle != nil) + } + // NOTE(laytan): I believe it is fine to close this handle right after CreateProcess, + // and we don't have to hold onto this until the process exits. + defer if null_handle != nil { + win32.CloseHandle(null_handle) + } + + if desc.stdout == nil { + stdout_handle = null_handle + } else { + stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) + } + + if desc.stderr == nil { + stderr_handle = null_handle + } else { + stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + + if desc.stdin == nil { + stdin_handle = null_handle + } else { + stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) + } + + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil + process_info: win32.PROCESS_INFORMATION + ok := win32.CreateProcessW( + nil, + command_line_w, + nil, + nil, + true, + win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, + raw_data(environment_block_w), + working_dir_w, + &win32.STARTUPINFOW{ + cb = size_of(win32.STARTUPINFOW), + hStdError = stderr_handle, + hStdOutput = stdout_handle, + hStdInput = stdin_handle, + dwFlags = win32.STARTF_USESTDHANDLES, + }, + &process_info, + ) + if !ok { + err = _get_platform_error() + return + } + process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + handle := win32.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE + + switch win32.WaitForSingleObject(handle, timeout_ms) { + case win32.WAIT_OBJECT_0: + exit_code: u32 + if !win32.GetExitCodeProcess(handle, &exit_code) { + err =_get_platform_error() + return + } + time_created: win32.FILETIME + time_exited: win32.FILETIME + time_kernel: win32.FILETIME + time_user: win32.FILETIME + if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + err = _get_platform_error() + return + } + process_state = { + exit_code = int(exit_code), + exited = true, + pid = process.pid, + success = true, + system_time = _filetime_to_duration(time_kernel), + user_time = _filetime_to_duration(time_user), + } + return + case win32.WAIT_TIMEOUT: + err = General_Error.Timeout + return + case: + err = _get_platform_error() + return + } +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if !win32.CloseHandle(win32.HANDLE(process.handle)) { + return _get_platform_error() + } + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + // Note(flysand): This is different than what the task manager's "kill process" + // functionality does, as we don't try to send WM_CLOSE message first. This + // is quite a rough way to kill the process, which should be consistent with + // linux. The error code 9 is to mimic SIGKILL event. + if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { + return _get_platform_error() + } + return nil +} + +_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { + ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) + return time.Duration(ticks * 100) +} + +_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + if u32(pid) == entry.th32ProcessID { + return + } + status = win32.Process32NextW(snap, &entry) + } + err = General_Error.Not_Exist + return +} + +// Note(flysand): Not sure which way it's better to get the executable path: +// via toolhelp snapshots or by reading other process' PEB memory. I have +// a slight suspicion that if both exe path and command line are desired, +// it's faster to just read both from PEB, but maybe the toolhelp snapshots +// are just better...? +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + snap := win32.CreateToolhelp32Snapshot( + win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == win32.INVALID_HANDLE_VALUE { + err =_get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } + status := win32.Module32FirstW(snap, &entry) + if !status { + err =_get_platform_error() + return + } + return win32_wstring_to_utf8(cstring16(raw_data(entry.szExePath[:])), allocator) +} + +_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + token_handle: win32.HANDLE + if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { + err = _get_platform_error() + return + } + token_user_size: u32 + if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + // Note(flysand): Make sure the buffer too small error comes out, and not any other error + err = _get_platform_error() + if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { + return + } + err = nil + } + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return)) + if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + err = _get_platform_error() + return + } + + sid_type: win32.SID_NAME_USE + username_w: [256]u16 + domain_w: [256]u16 + username_chrs := u32(256) + domain_chrs := u32(256) + + if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + err = _get_platform_error() + return + } + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return + return strings.concatenate({domain, "\\", username}, allocator) +} + +_parse_command_line :: proc(cmd_line_w: cstring16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { + argc: i32 + argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) + if argv_w == nil { + return nil, _get_platform_error() + } + argv = make([]string, argc, allocator) or_return + defer if err != nil { + for arg in argv { + delete(arg, allocator) + } + delete(argv, allocator) + } + for arg_w, i in argv_w[:argc] { + argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return + } + return +} + +_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { + _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { + for _ in 0 ..< n { + strings.write_byte(builder, b) + } + } + builder := strings.builder_make(allocator) + for arg, i in command { + if i != 0 { + strings.write_byte(&builder, ' ') + } + j := 0 + if strings.contains_any(arg, "()[]{}^=;!'+,`~\" ") { + strings.write_byte(&builder, '"') + for j < len(arg) { + backslashes := 0 + for j < len(arg) && arg[j] == '\\' { + backslashes += 1 + j += 1 + } + if j == len(arg) { + _write_byte_n_times(&builder, '\\', 2*backslashes) + break + } else if arg[j] == '"' { + _write_byte_n_times(&builder, '\\', 2*backslashes+1) + strings.write_byte(&builder, arg[j]) + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } + j += 1 + } + strings.write_byte(&builder, '"') + } else { + strings.write_string(&builder, arg) + } + } + return strings.to_string(builder) +} + +_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) { + zt_count := 0 + for idx := 0; true; { + if block[idx] == 0x0000 { + zt_count += 1 + if block[idx+1] == 0x0000 { + zt_count += 1 + break + } + } + idx += 1 + } + + // Note(flysand): Each string in the environment block is terminated + // by a NUL character. In addition, the environment block itself is + // terminated by a NUL character. So the number of strings in the + // environment block is the number of NUL character minus the + // block terminator. + env_count := zt_count - 1 + envs = make([]string, env_count, allocator) or_return + defer if err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + env_idx := 0 + last_idx := 0 + idx := 0 + for block[idx] != 0x0000 { + for block[idx] != 0x0000 { + idx += 1 + } + env_w := block[last_idx:idx] + envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return + env_idx += 1 + idx += 1 + last_idx = idx + } + return +} + +_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { + builder := strings.builder_make(allocator) + loop: #reverse for kv, cur_idx in environment { + eq_idx := strings.index_byte(kv, '=') + assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") + key := kv[:eq_idx] + for old_kv in environment[cur_idx+1:] { + old_key := old_kv[:strings.index_byte(old_kv, '=')] + if key == old_key { + continue loop + } + } + strings.write_string(&builder, kv) + strings.write_byte(&builder, 0) + } + // Note(flysand): In addition to the NUL-terminator for each string, the + // environment block itself is NUL-terminated. + strings.write_byte(&builder, 0) + return strings.to_string(builder) +} 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 +} + +/* + `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 +} -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) +is_reserved_name :: proc(path: string) -> bool { + return _is_reserved_name(path) +} \ No newline at end of file diff --git a/core/os/stat_js.odin b/core/os/stat_js.odin new file mode 100644 index 000000000..e37864936 --- /dev/null +++ b/core/os/stat_js.odin @@ -0,0 +1,25 @@ +#+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 +} + +_is_reserved_name :: proc(path: string) -> bool { + return false +} \ No newline at end of file diff --git a/core/os/stat_linux.odin b/core/os/stat_linux.odin new file mode 100644 index 000000000..dc5bccb54 --- /dev/null +++ b/core/os/stat_linux.odin @@ -0,0 +1,79 @@ +#+private +package os2 + +import "core:time" +import "base:runtime" +import "core:sys/linux" + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { + impl := (^File_Impl)(f.impl) + return _fstat_internal(impl.fd, allocator) +} + +_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + s: linux.Stat + errno := linux.fstat(fd, &s) + if errno != .NONE { + return {}, _get_platform_error(errno) + } + type := File_Type.Regular + switch s.mode & linux.S_IFMT { + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket + } + mode := transmute(Permissions)(0o7777 & transmute(u32)s.mode) + + // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time + fi = File_Info { + fullpath = _get_full_path(fd, allocator) or_return, + name = "", + inode = u128(u64(s.ino)), + size = i64(s.size), + mode = mode, + type = type, + modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this + } + fi.creation_time = fi.modification_time + _, fi.name = split_path(fi.fullpath) + return +} + +// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + + fd, errno := linux.open(name_cstr, {}) + if errno != .NONE { + return {}, _get_platform_error(errno) + } + defer linux.close(fd) + return _fstat_internal(fd, allocator) +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return + + fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) + if errno != .NONE { + return {}, _get_platform_error(errno) + } + defer linux.close(fd) + return _fstat_internal(fd, allocator) +} + +_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/stat_posix.odin b/core/os/stat_posix.odin new file mode 100644 index 000000000..e401ffe40 --- /dev/null +++ b/core/os/stat_posix.odin @@ -0,0 +1,141 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" +import "core:time" + +internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + _, fi.name = split_path(fi.fullpath) + + fi.inode = u128(stat.st_ino) + fi.size = i64(stat.st_size) + + fi.mode = transmute(Permissions)u32(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT)) + + fi.type = .Undetermined + switch { + case posix.S_ISBLK(stat.st_mode): + fi.type = .Block_Device + case posix.S_ISCHR(stat.st_mode): + fi.type = .Character_Device + case posix.S_ISDIR(stat.st_mode): + fi.type = .Directory + case posix.S_ISFIFO(stat.st_mode): + fi.type = .Named_Pipe + case posix.S_ISLNK(stat.st_mode): + fi.type = .Symlink + case posix.S_ISREG(stat.st_mode): + fi.type = .Regular + case posix.S_ISSOCK(stat.st_mode): + fi.type = .Socket + } + + fi.creation_time = timespec_time(stat.st_birthtimespec) + fi.modification_time = timespec_time(stat.st_mtim) + fi.access_time = timespec_time(stat.st_atim) + + timespec_time :: proc(t: posix.timespec) -> time.Time { + return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)} + } + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat: posix.stat_t + if posix.fstat(impl.fd, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return + + fd := posix.open(cname, {}) + if fd == -1 { + err = _get_platform_error() + return + } + defer posix.close(fd) + + fullpath := _posix_absolute_path(fd, name, allocator) or_return + + stat: posix.stat_t + if posix.stat(fullpath, &stat) != .OK { + err = _get_platform_error() + return + } + + return internal_stat(stat, string(fullpath)), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. + + // NOTE: This might not be correct when given "/symlink/foo.txt", + // you would want that to resolve "/symlink", but not resolve "foo.txt". + + fullpath := clean_path(name, temp_allocator) or_return + assert(len(fullpath) > 0) + switch { + case fullpath[0] == '/': + // nothing. + case fullpath == ".": + fullpath = getwd(temp_allocator) or_return + case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': + fullpath = fullpath[2:] + fallthrough + case: + fullpath = concatenate({ + getwd(temp_allocator) or_return, + "/", + fullpath, + }, temp_allocator) or_return + } + + stat: posix.stat_t + c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return + if posix.lstat(c_fullpath, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath = clone_string(fullpath, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_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/stat_unix.odin b/core/os/stat_unix.odin deleted file mode 100644 index 648987a07..000000000 --- a/core/os/stat_unix.odin +++ /dev/null @@ -1,134 +0,0 @@ -#+build linux, darwin, freebsd, openbsd, netbsd, haiku -package os - -import "core:time" - -/* -For reference -------------- - -Unix_File_Time :: struct { - seconds: i64, - nanoseconds: i64, -} - -Stat :: struct { - device_id: u64, // ID of device containing file - serial: u64, // File serial number - nlink: u64, // Number of hard links - mode: u32, // Mode of the file - uid: u32, // User ID of the file's owner - gid: u32, // Group ID of the file's group - _padding: i32, // 32 bits of padding - rdev: u64, // Device ID, if device - size: i64, // Size of the file, in bytes - block_size: i64, // Optimal bllocksize for I/O - blocks: i64, // Number of 512-byte blocks allocated - - last_access: Unix_File_Time, // Time of last access - modified: Unix_File_Time, // Time of last modification - status_change: Unix_File_Time, // Time of last status change - - _reserve1, - _reserve2, - _reserve3: i64, -}; - -Time :: struct { - _nsec: i64, // zero is 1970-01-01 00:00:00 -} - -File_Info :: struct { - fullpath: string, - name: string, - size: i64, - mode: File_Mode, - is_dir: bool, - creation_time: time.Time, - modification_time: time.Time, - access_time: time.Time, -} -*/ - -@(private, require_results) -_make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time { - return time.Time{ - _nsec = i64(uft.nanoseconds) + i64(uft.seconds) * 1_000_000_000, - } -} - -@(private) -_fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { - fi.size = s.size - fi.mode = cast(File_Mode)s.mode - fi.is_dir = S_ISDIR(s.mode) - - // NOTE(laleksic, 2021-01-21): Not really creation time, but closest we can get (maybe better to leave it 0?) - fi.creation_time = _make_time_from_unix_file_time(s.status_change) - - fi.modification_time = _make_time_from_unix_file_time(s.modified) - fi.access_time = _make_time_from_unix_file_time(s.last_access) -} - - -@(private, require_results) -path_base :: proc(path: string) -> string { - is_separator :: proc(c: byte) -> bool { - return c == '/' - } - - if path == "" { - return "." - } - - path := path - for len(path) > 0 && is_separator(path[len(path)-1]) { - path = path[:len(path)-1] - } - - i := len(path)-1 - for i >= 0 && !is_separator(path[i]) { - i -= 1 - } - if i >= 0 { - path = path[i+1:] - } - if path == "" { - return "/" - } - return path -} - - -@(require_results) -lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { - context.allocator = allocator - - s := _lstat(name) or_return - _fill_file_info_from_stat(&fi, s) - fi.fullpath = absolute_path_from_relative(name) or_return - fi.name = path_base(fi.fullpath) - return -} - -@(require_results) -stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { - context.allocator = allocator - - s := _stat(name) or_return - _fill_file_info_from_stat(&fi, s) - fi.fullpath = absolute_path_from_relative(name) or_return - fi.name = path_base(fi.fullpath) - return -} - -@(require_results) -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) { - context.allocator = allocator - - s := _fstat(fd) or_return - _fill_file_info_from_stat(&fi, s) - fi.fullpath = absolute_path_from_handle(fd) or_return - fi.name = path_base(fi.fullpath) - return -} diff --git a/core/os/stat_wasi.odin b/core/os/stat_wasi.odin new file mode 100644 index 000000000..f15479e22 --- /dev/null +++ b/core/os/stat_wasi.odin @@ -0,0 +1,104 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sys/wasm/wasi" +import "core:time" + +internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + _, fi.name = split_path(fi.fullpath) + + fi.inode = u128(stat.ino) + fi.size = i64(stat.size) + + switch stat.filetype { + case .BLOCK_DEVICE: fi.type = .Block_Device + case .CHARACTER_DEVICE: fi.type = .Character_Device + case .DIRECTORY: fi.type = .Directory + case .REGULAR_FILE: fi.type = .Regular + case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket + case .SYMBOLIC_LINK: fi.type = .Symlink + case .UNKNOWN: fi.type = .Undetermined + case: fi.type = .Undetermined + } + + fi.creation_time = time.Time{_nsec=i64(stat.ctim)} + fi.modification_time = time.Time{_nsec=i64(stat.mtim)} + fi.access_time = time.Time{_nsec=i64(stat.atim)} + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat, _err := wasi.fd_filestat_get(__fd(f)) + if _err != nil { + err = _get_platform_error(_err) + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_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/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 - - name := name - if name == "" { - name = "." +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || (^File_Impl)(f.impl).fd == nil { + return } - 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 + + 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), } - resize(&buf, len(buf)*2) + return } - return + return _file_info_from_get_file_information_by_handle(path, h, allocator) } -@(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 +_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 = "." } - context.allocator = allocator + 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) +} - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == 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 := 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) +_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) } -@(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_u16 :: proc(f: ^File) -> ([]u16, Error) { + if f == nil { + return nil, nil } - h := win32.HANDLE(fd) + h := _handle(f) n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) if n == 0 { - return nil, get_last_error() + return nil, _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 + + 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 } -@(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 "" + +_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) { - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) +filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) { + return {_nsec=(i64(ft) - 116444736000000000) * 100} +} - fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_dir = fi.mode & File_Mode_Dir != 0 +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) +} - windows_set_file_info_times(&fi, d) +filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li} - fi.fullpath, e = full_path_from_name(name) +_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 } -@(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 + 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 +} - windows_set_file_info_times(&fi, &d) +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", +} - return fi, nil +_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/stream.odin b/core/os/stream.odin deleted file mode 100644 index f4e9bcdde..000000000 --- a/core/os/stream.odin +++ /dev/null @@ -1,77 +0,0 @@ -package os - -import "core:io" - -stream_from_handle :: proc(fd: Handle) -> io.Stream { - s: io.Stream - s.data = rawptr(uintptr(fd)) - s.procedure = _file_stream_proc - return s -} - - -@(private) -_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(stream_data)) - n_int: int - os_err: Error - switch mode { - case .Close: - os_err = close(fd) - case .Flush: - os_err = flush(fd) - case .Read: - if len(p) == 0 { - return 0, nil - } - n_int, os_err = read(fd, p) - n = i64(n_int) - if n == 0 && os_err == nil { - err = .EOF - } - - case .Read_At: - if len(p) == 0 { - return 0, nil - } - n_int, os_err = read_at(fd, p, offset) - n = i64(n_int) - if n == 0 && os_err == nil { - err = .EOF - } - case .Write: - if len(p) == 0 { - return 0, nil - } - n_int, os_err = write(fd, p) - n = i64(n_int) - if n == 0 && os_err == nil { - err = .EOF - } - case .Write_At: - if len(p) == 0 { - return 0, nil - } - n_int, os_err = write_at(fd, p, offset) - n = i64(n_int) - if n == 0 && os_err == nil { - err = .EOF - } - case .Seek: - n, os_err = seek(fd, offset, int(whence)) - case .Size: - n, os_err = file_size(fd) - case .Destroy: - err = .Unsupported - case .Query: - return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) - } - - if err == nil && os_err != nil { - err = error_to_io_error(os_err) - } - if err != nil { - n = 0 - } - return -} diff --git a/core/os/temp_file.odin b/core/os/temp_file.odin new file mode 100644 index 000000000..2c0236428 --- /dev/null +++ b/core/os/temp_file.odin @@ -0,0 +1,110 @@ +package os2 + +import "base:runtime" + +@(private="file") +MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? + +// Creates a new temperatory file in the directory `dir`. +// +// Opens the file for reading and writing, with `Permissions_Read_Write_All` permissions, and returns the new `^File`. +// The filename is generated by taking a pattern, and adding a randomized string to the end. +// If the pattern includes an "*", the random string replaces the last "*". +// If `dir` is an empty string, `temp_directory()` will be used. +// +// The caller must `close` the file once finished with. +@(require_results) +create_temp_file :: proc(dir, pattern: string, additional_flags: File_Flags = {}) -> (f: ^File, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return + + rand_buf: [10]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + f, err = open(name, {.Read, .Write, .Create, .Excl} + additional_flags, Permissions_Read_Write_All) + if err == .Exist { + close(f) + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return nil, err + } + return f, err + } +} + +mkdir_temp :: make_directory_temp +// Creates a new temporary directory in the directory `dir`, and returns the path of the new directory. +// +// The directory name is generated by taking a pattern, and adding a randomized string to the end. +// If the pattern includes an "*", the random string replaces the last "*". +// If `dir` is an empty tring, `temp_directory()` will be used. +@(require_results) +make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return + + rand_buf: [10]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + err = make_directory(name, 0o700) + if err == nil { + return clone_string(name, allocator) + } + if err == .Exist { + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return "", err + } + if err == .Not_Exist { + if _, serr := stat(dir, temp_allocator); serr == .Not_Exist { + return "", serr + } + } + return "", err + } + +} + +temp_dir :: temp_directory + +/* + Returns the default directory to use for temporary files. + + On Unix systems, it typically returns $TMPDIR if non-empty, otherwlse `/tmp`. + On Windows, it uses `GetTempPathW`, returning the first non-empty value from one of the following: + * `%TMP%` + * `%TEMP%` + * `%USERPROFILE %` + * or the Windows directory + See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw for more information. + On wasi, it returns `/tmp`. +*/ +@(require_results) +temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { + return _temp_dir(allocator) +} + + + +@(private="file") +temp_join_path :: proc(dir, name: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { + return concatenate({dir, name}, allocator) + } + + return concatenate({dir, Path_Separator_String, name}, allocator) +} diff --git a/core/os/temp_file_js.odin b/core/os/temp_file_js.odin new file mode 100644 index 000000000..e1f2b3d95 --- /dev/null +++ b/core/os/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 diff --git a/core/os/temp_file_linux.odin b/core/os/temp_file_linux.odin new file mode 100644 index 000000000..310720cbe --- /dev/null +++ b/core/os/temp_file_linux.odin @@ -0,0 +1,13 @@ +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + tmpdir := get_env("TMPDIR", temp_allocator) + if tmpdir == "" { + tmpdir = "/tmp" + } + return clone_string(tmpdir, allocator) +} diff --git a/core/os/temp_file_posix.odin b/core/os/temp_file_posix.odin new file mode 100644 index 000000000..b44ea13a7 --- /dev/null +++ b/core/os/temp_file_posix.odin @@ -0,0 +1,20 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +@(require) +import "core:sys/posix" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + if tmp, ok := _lookup_env("TMPDIR", allocator); ok { + return tmp, nil + } + + when #defined(posix.P_tmpdir) { + return clone_string(posix.P_tmpdir, allocator) + } + + return clone_string("/tmp/", allocator) +} diff --git a/core/os/temp_file_wasi.odin b/core/os/temp_file_wasi.odin new file mode 100644 index 000000000..d5628d300 --- /dev/null +++ b/core/os/temp_file_wasi.odin @@ -0,0 +1,9 @@ +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + // NOTE: requires user to add /tmp to their preopen dirs, no standard way exists. + return clone_string("/tmp", allocator) +} diff --git a/core/os/temp_file_windows.odin b/core/os/temp_file_windows.odin new file mode 100644 index 000000000..91ea284a1 --- /dev/null +++ b/core/os/temp_file_windows.odin @@ -0,0 +1,23 @@ +#+private +package os2 + +import "base:runtime" +import win32 "core:sys/windows" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + n := win32.GetTempPathW(0, nil) + if n == 0 { + return "", nil + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + b := make([]u16, max(win32.MAX_PATH, n), temp_allocator) + n = win32.GetTempPathW(u32(len(b)), cstring16(raw_data(b))) + + if n == 3 && b[1] == ':' && b[2] == '\\' { + + } else if n > 0 && b[n-1] == '\\' { + n -= 1 + } + return win32_utf16_to_utf8(string16(b[:n]), allocator) +} diff --git a/core/os/user.odin b/core/os/user.odin new file mode 100644 index 000000000..e2a4ec4d0 --- /dev/null +++ b/core/os/user.odin @@ -0,0 +1,149 @@ +package os2 + +import "base:runtime" + +// ``` +// Windows: C:\Users\Alice +// macOS: /Users/Alice +// Linux: /home/alice +// ``` +@(require_results) +user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_home_dir(allocator) +} + +// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches +// +// Sometimes deleted for system maintenance +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Caches +// Linux: /home/alice/.cache +// ``` +@(require_results) +user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_cache_dir(allocator) +} + +// User-hidden application data +// +// ``` +// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.local/share +// ``` +// +// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* +@(require_results) +user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { + return _user_data_dir(allocator, roaming) +} + +// Non-essential application data, e.g. history, ui layout state +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.local/state +// ``` +@(require_results) +user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_state_dir(allocator) +} + +// Application log files +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Logs +// Linux: /home/alice/.local/state +// ``` +@(require_results) +user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_log_dir(allocator) +} + +// Application settings/preferences +// +// ``` +// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.config +// ``` +// +// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* +@(require_results) +user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { + return _user_config_dir(allocator, roaming) +} + +// ``` +// Windows: C:\Users\Alice\Music +// macOS: /Users/Alice/Music +// Linux: /home/alice/Music +// ``` +@(require_results) +user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_music_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Desktop +// macOS: /Users/Alice/Desktop +// Linux: /home/alice/Desktop +// ``` +@(require_results) +user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_desktop_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Documents +// macOS: /Users/Alice/Documents +// Linux: /home/alice/Documents +// ``` +@(require_results) +user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_documents_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Downloads +// macOS: /Users/Alice/Downloads +// Linux: /home/alice/Downloads +// ``` +@(require_results) +user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_downloads_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Pictures +// macOS: /Users/Alice/Pictures +// Linux: /home/alice/Pictures +// ``` +@(require_results) +user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_pictures_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Public +// macOS: /Users/Alice/Public +// Linux: /home/alice/Public +// ``` +@(require_results) +user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_public_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Videos +// macOS: /Users/Alice/Movies +// Linux: /home/alice/Videos +// ``` +@(require_results) +user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_videos_dir(allocator) +} \ No newline at end of file diff --git a/core/os/user_posix.odin b/core/os/user_posix.odin new file mode 100644 index 000000000..fa173f129 --- /dev/null +++ b/core/os/user_posix.odin @@ -0,0 +1,176 @@ +#+build !windows +package os2 + +import "base:intrinsics" +import "base:runtime" +import "core:strings" + +_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Caches", allocator) + case: // Unix + return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator) + } +} + +_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator) + } +} + +_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) + } +} + +_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Logs", allocator) + case: // Unix + return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) + } +} + +_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator) + } +} + +_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Music", allocator) + case: // Unix + return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator) + } +} + +_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Desktop", allocator) + case: // Unix + return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator) + } +} + +_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Documents", allocator) + case: // Unix + return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator) + } +} + +_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Downloads", allocator) + case: // Unix + return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator) + } +} + +_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Pictures", allocator) + case: // Unix + return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator) + } +} + +_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Public", allocator) + case: // Unix + return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator) + } +} + +_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Movies", allocator) + case: // Unix + return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator) + } +} + +_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + if v := get_env("HOME", allocator); v != "" { + return v, nil + } + err = .No_HOME_Variable + return +} + +_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + if xdg_key == "" { // Darwin doesn't have XDG paths. + dir = get_env("HOME", temp_allocator) + if dir == "" { + err = .No_HOME_Variable + return + } + return concatenate({dir, fallback_suffix}, allocator) + } else { + if strings.ends_with(xdg_key, "_DIR") { + dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return + } else { + dir = get_env(xdg_key, allocator) + } + + if dir == "" { + dir = get_env("HOME", temp_allocator) + if dir == "" { + err = .No_HOME_Variable + return + } + dir = concatenate({dir, fallback_suffix}, allocator) or_return + } + return + } +} + +// If `/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""` +_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + config_dir := user_config_dir(temp_allocator) or_return + user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return + content := read_entire_file(user_dirs_path, temp_allocator) or_return + + xdg_dirs := string(content) + for line in strings.split_lines_iterator(&xdg_dirs) { + if len(line) > 0 && line[0] == '#' { + continue + } + + equals := strings.index(line, "=") + if equals > -1 { + if line[:equals] == xdg_key { + // Unquote to return a bare path string as we do on Windows + val := strings.trim(line[equals+1:], "\"") + return replace_environment_placeholders(val, allocator), nil + } + } + } + return +} \ No newline at end of file diff --git a/core/os/user_windows.odin b/core/os/user_windows.odin new file mode 100644 index 000000000..75d0ba6ac --- /dev/null +++ b/core/os/user_windows.odin @@ -0,0 +1,78 @@ +package os2 + +import "base:runtime" +@(require) import win32 "core:sys/windows" + +_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_LocalAppData + return _get_known_folder_path(&guid, allocator) +} + +_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) { + guid := win32.FOLDERID_LocalAppData + if roaming { + guid = win32.FOLDERID_RoamingAppData + } + return _get_known_folder_path(&guid, allocator) +} + +_user_config_dir :: _local_appdata_or_roaming +_user_data_dir :: _local_appdata_or_roaming + +_user_state_dir :: _local_appdata +_user_log_dir :: _local_appdata +_user_cache_dir :: _local_appdata + +_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Profile + return _get_known_folder_path(&guid, allocator) +} + +_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Music + return _get_known_folder_path(&guid, allocator) +} + +_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Desktop + return _get_known_folder_path(&guid, allocator) +} + +_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Documents + return _get_known_folder_path(&guid, allocator) +} + +_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Downloads + return _get_known_folder_path(&guid, allocator) +} + +_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Pictures + return _get_known_folder_path(&guid, allocator) +} + +_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Public + return _get_known_folder_path(&guid, allocator) +} + +_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Videos + return _get_known_folder_path(&guid, allocator) +} + +_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) { + // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + // See also `known_folders.odin` in `core:sys/windows` for the GUIDs. + path_w: win32.LPWSTR + res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w) + defer win32.CoTaskMemFree(path_w) + + if res != 0 { + return "", .Invalid_Path + } + + return win32_wstring_to_utf8(cstring16(path_w), allocator) +} \ No newline at end of file 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/dir.odin b/tests/core/os/dir.odin new file mode 100644 index 000000000..464abed98 --- /dev/null +++ b/tests/core/os/dir.odin @@ -0,0 +1,116 @@ +package tests_core_os + +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) + defer delete(path) + + fis, err_read := os.read_all_directory_by_path(path, context.allocator) + defer os.file_info_slice_delete(fis, context.allocator) + + slice.sort_by_key(fis, proc(fi: os.File_Info) -> string { return fi.name }) + + if err_read == .Unsupported { + log.warn("core:os directory functionality is unsupported, skipping test") + return + } + + testing.expect_value(t, err_join, nil) + testing.expect_value(t, err_read, nil) + testing.expect_value(t, len(fis), 2) + + testing.expect_value(t, fis[0].name, "b.txt") + testing.expect_value(t, fis[0].type, os.File_Type.Regular) + + testing.expect_value(t, fis[1].name, "sub") + testing.expect_value(t, fis[1].type, os.File_Type.Directory) +} + +@(test) +test_walker :: proc(t: ^testing.T) { + path, err := os.join_path({#directory, "dir"}, context.allocator) + defer delete(path) + testing.expect_value(t, err, nil) + + w := os.walker_create(path) + defer os.walker_destroy(&w) + + test_walker_internal(t, &w) +} + +@(test) +test_walker_file :: proc(t: ^testing.T) { + path, err_join := os.join_path({#directory, "dir"}, context.allocator) + defer delete(path) + testing.expect_value(t, err_join, nil) + + f, err_open := os.open(path) + testing.expect_value(t, err_open, nil) + defer os.close(f) + + w := os.walker_create(f) + defer os.walker_destroy(&w) + + test_walker_internal(t, &w) +} + +test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) { + Seen :: struct { + type: os.File_Type, + path: string, + } + + joined_1, err_joined_1 := os.join_path({"dir", "b.txt"}, context.allocator) + joined_2, err_joined_2 := os.join_path({"dir", "sub"}, context.allocator) + joined_3, err_joined_3 := os.join_path({"dir", "sub", ".gitkeep"}, context.allocator) + + testing.expect_value(t, err_joined_1, nil) + testing.expect_value(t, err_joined_2, nil) + testing.expect_value(t, err_joined_3, nil) + + expected := [?]Seen{ + {.Regular, joined_1}, + {.Directory, joined_2}, + {.Regular, joined_3}, + } + + seen: [dynamic]Seen + defer delete(seen) + + for info in os.walker_walk(w) { + + errpath, err := os.walker_error(w) + testing.expectf(t, err == nil, "walker error for %q: %v", errpath, err) + + append(&seen, Seen{ + info.type, + strings.clone(info.fullpath), + }) + } + + if _, err := os.walker_error(w); err == .Unsupported { + log.warn("core:os directory functionality is unsupported, skipping test") + return + } + + testing.expect_value(t, len(seen), len(expected)) + + for expectation in expected { + found: bool + for entry in seen { + if strings.has_suffix(entry.path, expectation.path) { + found = true + testing.expect_value(t, entry.type, expectation.type) + delete(entry.path) + } + } + testing.expectf(t, found, "%q not found in %v", expectation, seen) + delete(expectation.path) + } +} diff --git a/tests/core/os/file.odin b/tests/core/os/file.odin new file mode 100644 index 000000000..aed57c26c --- /dev/null +++ b/tests/core/os/file.odin @@ -0,0 +1,33 @@ +package tests_core_os + +import "core:os" +import "core:testing" + +@(test) +test_clone :: proc(t: ^testing.T) { + joined, err := os.join_path({#directory, "file.odin"}, context.temp_allocator) + testing.expect_value(t, err, nil) + f: ^os.File + f, err = os.open(joined) + testing.expect_value(t, err, nil) + testing.expect(t, f != nil) + + clone: ^os.File + clone, err = os.clone(f) + testing.expect_value(t, err, nil) + testing.expect(t, clone != nil) + + testing.expect_value(t, os.name(clone), os.name(f)) + testing.expect(t, os.fd(clone) != os.fd(f)) + + os.close(f) + + buf: [128]byte + n: int + n, err = os.read(clone, buf[:]) + testing.expect_value(t, err, nil) + testing.expect(t, n > 13) + testing.expect_value(t, string(buf[:13]), "package tests") + + os.close(clone) +} diff --git a/tests/core/os/old/os.odin b/tests/core/os/old/os.odin new file mode 100644 index 000000000..9925cf708 --- /dev/null +++ b/tests/core/os/old/os.odin @@ -0,0 +1,63 @@ +package test_core_os_old + +import "core:c/libc" +import win32 "core:sys/windows" +import os "core:os/old" +import "core:slice" +import "core:testing" +import "core:log" + +_ :: libc +_ :: win32 + +@(test) +read_dir :: proc(t: ^testing.T) { + when ODIN_OS == .Windows { + link := win32.utf8_to_wstring(#directory + "dir/alink.txt") + target := win32.utf8_to_wstring(#directory + "dir/a.txt") + sym_err := win32.CreateSymbolicLinkW(link, target, win32.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) + + if !sym_err { + log.infof("Unable to create symlink, skipping test. Error: %v", win32.GetLastError()) + return + } + } else { + sym_err := libc.system("ln -s " + #directory + "dir/a.txt " + #directory + "dir/alink.txt") + if sym_err != 0 { + log.infof("Unable to create symlink, skipping test. Error: %v", sym_err) + return + } + } + defer os.remove(#directory + "dir/alink.txt") + + fd, err := os.open(#directory + "/dir") + testing.expect_value(t, err, nil) + defer { + testing.expect_value(t, os.close(fd), nil) + } + + dir, err2 := os.read_dir(fd, -1) + testing.expect_value(t, err2, nil) + defer os.file_info_slice_delete(dir) + + slice.sort_by_key(dir, proc(fi: os.File_Info) -> string { return fi.name }) + + testing.expect_value(t, len(dir), 3) + + if len(dir) > 0 { + testing.expect_value(t, dir[0].name, "alink.txt") + testing.expect(t, !dir[0].is_dir, "is a directory") + when ODIN_OS == .Windows { + testing.expect(t, dir[0].mode & os.File_Mode_Sym_Link != 0, "not a symlink") + } else { + testing.expect(t, os.S_ISLNK(auto_cast dir[0].mode), "not a symlink") + } + } + if len(dir) > 1 { + testing.expect_value(t, dir[1].name, "b.txt") + } + if len(dir) > 2 { + testing.expect_value(t, dir[2].name, "sub") + testing.expect(t, dir[2].is_dir, "is not a directory") + } +} diff --git a/tests/core/os/os.odin b/tests/core/os/os.odin deleted file mode 100644 index 1510bad31..000000000 --- a/tests/core/os/os.odin +++ /dev/null @@ -1,63 +0,0 @@ -package test_core_os - -import "core:c/libc" -import win32 "core:sys/windows" -import "core:os" -import "core:slice" -import "core:testing" -import "core:log" - -_ :: libc -_ :: win32 - -@(test) -read_dir :: proc(t: ^testing.T) { - when ODIN_OS == .Windows { - link := win32.utf8_to_wstring(#directory + "dir/alink.txt") - target := win32.utf8_to_wstring(#directory + "dir/a.txt") - sym_err := win32.CreateSymbolicLinkW(link, target, win32.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) - - if !sym_err { - log.infof("Unable to create symlink, skipping test. Error: %v", win32.GetLastError()) - return - } - } else { - sym_err := libc.system("ln -s " + #directory + "dir/a.txt " + #directory + "dir/alink.txt") - if sym_err != 0 { - log.infof("Unable to create symlink, skipping test. Error: %v", sym_err) - return - } - } - defer os.remove(#directory + "dir/alink.txt") - - fd, err := os.open(#directory + "/dir") - testing.expect_value(t, err, nil) - defer { - testing.expect_value(t, os.close(fd), nil) - } - - dir, err2 := os.read_dir(fd, -1) - testing.expect_value(t, err2, nil) - defer os.file_info_slice_delete(dir) - - slice.sort_by_key(dir, proc(fi: os.File_Info) -> string { return fi.name }) - - testing.expect_value(t, len(dir), 3) - - if len(dir) > 0 { - testing.expect_value(t, dir[0].name, "alink.txt") - testing.expect(t, !dir[0].is_dir, "is a directory") - when ODIN_OS == .Windows { - testing.expect(t, dir[0].mode & os.File_Mode_Sym_Link != 0, "not a symlink") - } else { - testing.expect(t, os.S_ISLNK(auto_cast dir[0].mode), "not a symlink") - } - } - if len(dir) > 1 { - testing.expect_value(t, dir[1].name, "b.txt") - } - if len(dir) > 2 { - testing.expect_value(t, dir[2].name, "sub") - testing.expect(t, dir[2].is_dir, "is not a directory") - } -} diff --git a/tests/core/os/os2/dir.odin b/tests/core/os/os2/dir.odin deleted file mode 100644 index 8ef333219..000000000 --- a/tests/core/os/os2/dir.odin +++ /dev/null @@ -1,116 +0,0 @@ -package tests_core_os_os2 - -import os "core:os/os2" -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) - defer delete(path) - - fis, err_read := os.read_all_directory_by_path(path, context.allocator) - defer os.file_info_slice_delete(fis, context.allocator) - - 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") - return - } - - testing.expect_value(t, err_join, nil) - testing.expect_value(t, err_read, nil) - testing.expect_value(t, len(fis), 2) - - testing.expect_value(t, fis[0].name, "b.txt") - testing.expect_value(t, fis[0].type, os.File_Type.Regular) - - testing.expect_value(t, fis[1].name, "sub") - testing.expect_value(t, fis[1].type, os.File_Type.Directory) -} - -@(test) -test_walker :: proc(t: ^testing.T) { - path, err := os.join_path({#directory, "../dir"}, context.allocator) - defer delete(path) - testing.expect_value(t, err, nil) - - w := os.walker_create(path) - defer os.walker_destroy(&w) - - test_walker_internal(t, &w) -} - -@(test) -test_walker_file :: proc(t: ^testing.T) { - path, err_join := os.join_path({#directory, "../dir"}, context.allocator) - defer delete(path) - testing.expect_value(t, err_join, nil) - - f, err_open := os.open(path) - testing.expect_value(t, err_open, nil) - defer os.close(f) - - w := os.walker_create(f) - defer os.walker_destroy(&w) - - test_walker_internal(t, &w) -} - -test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) { - Seen :: struct { - type: os.File_Type, - path: string, - } - - joined_1, err_joined_1 := os.join_path({"dir", "b.txt"}, context.allocator) - joined_2, err_joined_2 := os.join_path({"dir", "sub"}, context.allocator) - joined_3, err_joined_3 := os.join_path({"dir", "sub", ".gitkeep"}, context.allocator) - - testing.expect_value(t, err_joined_1, nil) - testing.expect_value(t, err_joined_2, nil) - testing.expect_value(t, err_joined_3, nil) - - expected := [?]Seen{ - {.Regular, joined_1}, - {.Directory, joined_2}, - {.Regular, joined_3}, - } - - seen: [dynamic]Seen - defer delete(seen) - - for info in os.walker_walk(w) { - - errpath, err := os.walker_error(w) - testing.expectf(t, err == nil, "walker error for %q: %v", errpath, err) - - append(&seen, Seen{ - info.type, - strings.clone(info.fullpath), - }) - } - - if _, err := os.walker_error(w); err == .Unsupported { - log.warn("os2 directory functionality is unsupported, skipping test") - return - } - - testing.expect_value(t, len(seen), len(expected)) - - for expectation in expected { - found: bool - for entry in seen { - if strings.has_suffix(entry.path, expectation.path) { - found = true - testing.expect_value(t, entry.type, expectation.type) - delete(entry.path) - } - } - testing.expectf(t, found, "%q not found in %v", expectation, seen) - delete(expectation.path) - } -} diff --git a/tests/core/os/os2/file.odin b/tests/core/os/os2/file.odin deleted file mode 100644 index 0152a2008..000000000 --- a/tests/core/os/os2/file.odin +++ /dev/null @@ -1,33 +0,0 @@ -package tests_core_os_os2 - -import os "core:os/os2" -import "core:testing" - -@(test) -test_clone :: proc(t: ^testing.T) { - joined, err := os.join_path({#directory, "file.odin"}, context.temp_allocator) - testing.expect_value(t, err, nil) - f: ^os.File - f, err = os.open(joined) - testing.expect_value(t, err, nil) - testing.expect(t, f != nil) - - clone: ^os.File - clone, err = os.clone(f) - testing.expect_value(t, err, nil) - testing.expect(t, clone != nil) - - testing.expect_value(t, os.name(clone), os.name(f)) - testing.expect(t, os.fd(clone) != os.fd(f)) - - os.close(f) - - buf: [128]byte - n: int - n, err = os.read(clone, buf[:]) - testing.expect_value(t, err, nil) - testing.expect(t, n > 13) - testing.expect_value(t, string(buf[:13]), "package tests") - - os.close(clone) -} diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin deleted file mode 100644 index 868023c86..000000000 --- a/tests/core/os/os2/path.odin +++ /dev/null @@ -1,562 +0,0 @@ -package tests_core_os_os2 - -import "core:fmt" -import os "core:os/os2" -import "core:log" -import "core:testing" -import "core:slice" -import "core:strings" - -@(test) -test_executable :: proc(t: ^testing.T) { - path, err := os.get_executable_path(context.allocator) - defer delete(path) - - log.infof("executable path: %q", path) - - // NOTE: some sanity checks that should always be the case, at least in the CI. - - testing.expect_value(t, err, nil) - testing.expect(t, len(path) > 0) - testing.expect(t, os.is_absolute_path(path)) - _, filename := os.split_path(os.args[0]) - testing.expectf(t, strings.contains(path, filename), "expected the executable path to contain the base of os.args[0] which is %q", filename) -} - -posix_to_dos_path :: proc(path: string) -> string { - if len(path) == 0 { - return path - } - path := path - path, _ = strings.replace_all(path, `/`, `\`, context.temp_allocator) - if path[0] == '\\' { - path = strings.concatenate({"C:", path}, context.temp_allocator) - } - return path -} - -@(test) -test_clean_path :: proc(t: ^testing.T) { - Test_Case :: struct{ - path: string, - expected: string, - } - - when ODIN_OS == .Windows { - test_cases := [?]Test_Case { - {`W:/odin\examples\demo/demo.odin`, `W:\odin\examples\demo\demo.odin`}, - {`\\server\share\path\file.ext`, `\\server\share\path\file.ext`}, - {`//server\share/path\file.ext`, `\\server\share\path\file.ext`}, - {`/\192.168.0.10\share/path\file.ext`, `\\192.168.0.10\share\path\file.ext`}, - {`\\?\C:/Users/Foo/path\file.ext`, `\\?\C:\Users\Foo\path\file.ext`}, - {`\\?\\localhost\share\file.ext`, `\\?\\localhost\share\file.ext`}, - {`//?\/192.168.0.10\share\file.ext`, `\\?\\192.168.0.10\share\file.ext`}, - {`\\.\PhysicalDrive3`, `\\.\PhysicalDrive3`}, - {`/\./PhysicalDrive3`, `\\.\PhysicalDrive3`}, - {`C:\a\..\..`, `C:\`}, - {`C:\a\..`, `C:\`}, - {`C:\あ/a/..`, `C:\あ`}, - {`C:\あ/a/../あ`, `C:\あ\あ`}, - } - } else { - test_cases := [?]Test_Case { - {`../../foo/../../`, `../../..`}, - {`../../foo/..`, `../..`}, - {`../../foo`, `../../foo`}, - {`../..`, `../..`}, - {`.././foo`, `../foo`}, - {`..`, `..`}, - {`.`, `.`}, - {`.foo`, `.foo`}, - {`/../../foo/../../`, `/`}, - {`/../`, `/`}, - {`/..`, `/`}, - {`/`, `/`}, - {`//home/foo/bar/../../`, `/home`}, - {`/a/../..`, `/`}, - {`/a/../`, `/`}, - {`/a/あ`, `/a/あ`}, - {`/a/あ/..`, `/a`}, - {`/あ/a/..`, `/あ`}, - {`/あ/a/../あ`, `/あ/あ`}, - {`/home/../`, `/`}, - {`/home/..`, `/`}, - {`/home/foo/../../usr`, `/usr`}, - {`/home/foo/../..`, `/`}, - {`/home/foo/../`, `/home`}, - {``, `.`}, - {`a/..`, `.`}, - {`a`, `a`}, - {`abc//.//../foo`, `foo`}, - {`foo`, `foo`}, - {`home/foo/bar/../../`, `home`}, - } - } - - for tc in test_cases { - joined, err := os.clean_path(tc.path, context.temp_allocator) - testing.expectf(t, joined == tc.expected && err == nil, "expected clean_path(%q) -> %q; got: %q, %v", tc.path, tc.expected, joined, err) - } -} - -@(test) -test_is_absolute_path :: proc(t: ^testing.T) { - when ODIN_OS == .Windows { - testing.expect(t, os.is_absolute_path(`C:\Windows`)) - } else { - testing.expect(t, os.is_absolute_path("/home")) - } - testing.expect(t, !os.is_absolute_path("home")) -} - -@(test) -test_get_relative_path :: proc(t: ^testing.T) { - Test_Case :: struct { - base, target: string, - expected: string, - } - - Fail_Case :: struct { - base, target: string, - } - - test_cases := [?]Test_Case { - {"", "foo", "foo"}, - {".", "foo", "foo"}, - {"/", "/", "."}, - {"/", "/home/alice/bert", "home/alice/bert"}, - {"/a", "/b", "../b"}, - {"/あ", "/あ/a", "a"}, - {"/a", "/a/あ", "あ"}, - {"/あ", "/い", "../い"}, - {"/a", "/usr", "../usr"}, - {"/home", "/", ".."}, - {"/home", "/home/alice/bert", "alice/bert"}, - {"/home/foo", "/", "../.."}, - {"/home/foo", "/home", ".."}, - {"/home/foo", "/home/alice/bert", "../alice/bert"}, - {"/home/foo", "/home/foo", "."}, - {"/home/foo", "/home/foo/bar", "bar"}, - {"/home/foo/bar", "/home", "../.."}, - {"/home/foo/bar", "/home/alice/bert", "../../alice/bert"}, - {"/home/foo/bar/bert", "/home/alice/bert", "../../../alice/bert"}, - {"/www", "/mount", "../mount"}, - {"foo", ".", ".."}, - {"foo", "bar", "../bar"}, - {"foo", "bar", "../bar"}, - {"foo", "../bar", "../../bar"}, - {"foo", "foo", "."}, - {"foo", "foo/bar", "bar"}, - {"home/foo/bar", "home/alice/bert", "../../alice/bert"}, - } - - fail_cases := [?]Fail_Case { - {"", "/home"}, - {"/home", ""}, - {"..", ""}, - } - - when ODIN_OS == .Windows { - for &tc in test_cases { - tc.base = posix_to_dos_path(tc.base) - tc.target = posix_to_dos_path(tc.target) - // Make one part all capitals to test case-insensitivity. - tc.target = strings.to_upper(tc.target, context.temp_allocator) - tc.expected = posix_to_dos_path(tc.expected) - } - for &tc in fail_cases { - tc.base = posix_to_dos_path(tc.base) - tc.target = posix_to_dos_path(tc.target) - } - } - - for tc in test_cases { - result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator) - joined, err2 := os.join_path({tc.base, result}, context.temp_allocator) - - when ODIN_OS == .Windows { - passed := strings.equal_fold(result, tc.expected) && err == nil - join_guaranteed := strings.equal_fold(joined, tc.target) && err2 == nil - } else { - passed := result == tc.expected && err == nil - join_guaranteed := joined == tc.target && err2 == nil - } - testing.expectf(t, passed, "expected get_relative_path(%q, %q) -> %q; got %q, %v", tc.base, tc.target, tc.expected, result, err) - testing.expectf(t, join_guaranteed, "join_path({{%q, %q}}) guarantee of get_relative_path(%q, %q) failed; got %q, %v instead", tc.base, result, tc.base, tc.target, joined, err2) - } - - for tc in fail_cases { - result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator) - testing.expectf(t, result == "" && err != nil, "expected get_relative_path(%q, %q) to fail, got %q, %v", tc.base, tc.target, result, err) - } -} - -@(test) -test_split_path :: proc(t: ^testing.T) { - Test_Case :: struct { - path: string, - dir, filename: string, - } - - test_cases := [?]Test_Case { - { "", "", "" }, - { "/", "/", "" }, - { "/a", "/", "a" }, - { "readme.txt", "", "readme.txt" }, - { "/readme.txt", "/", "readme.txt" }, - { "/var/readme.txt", "/var", "readme.txt" }, - { "/home/foo/bar.tar.gz", "/home/foo", "bar.tar.gz" }, - } - - when ODIN_OS == .Windows { - for &tc in test_cases { - tc.path = posix_to_dos_path(tc.path) - tc.dir = posix_to_dos_path(tc.dir) - tc.filename = posix_to_dos_path(tc.filename) - } - } - - for tc in test_cases { - dir, filename := os.split_path(tc.path) - testing.expectf(t, dir == tc.dir && filename == tc.filename, "expected split_path(%q) -> %q, %q; got: %q, %q", tc.path, tc.dir, tc.filename, dir, filename) - } -} - -@(test) -test_join_path :: proc(t: ^testing.T) { - Test_Case :: struct { - elems: []string, - expected: string, - } - - test_cases := [?]Test_Case { - { {"" }, "" }, - { {"/" }, "/" }, - { {"home" }, "home" }, - { {"home", "" }, "home" }, - { {"/home", "" }, "/home" }, - { {"", "home" }, "home" }, - { {"", "/home" }, "/home" }, - { {"", "/home", "", "foo" }, "/home/foo" }, - { {"", "home", "", "", "foo", "" }, "home/foo" }, - } - - when ODIN_OS == .Windows { - for &tc in test_cases { - for &elem in tc.elems { - elem = posix_to_dos_path(elem) - } - tc.expected = posix_to_dos_path(tc.expected) - } - } - - for tc in test_cases { - result, err := os.join_path(tc.elems, context.temp_allocator) - testing.expectf(t, result == tc.expected && err == nil, "expected join_path(%v) -> %q; got: %q, %v", tc.elems, tc.expected, result, err) - } -} - -@(test) -test_split_filename :: proc(t: ^testing.T) { - Test_Case :: struct { - filename: string, - base, ext: string, - } - - test_cases := [?]Test_Case { - {"", "", ""}, - {"a", "a", ""}, - {".", ".", ""}, - {".a", ".a", ""}, - {".foo", ".foo", ""}, - {".foo.txt", ".foo", "txt"}, - {"a.b", "a", "b"}, - {"foo", "foo", ""}, - {"readme.txt", "readme", "txt"}, - {"pkg.tar.gz", "pkg.tar", "gz"}, - // Assert API ignores directory hierarchies: - {"dir/FILE.TXT", "dir/FILE", "TXT"}, - } - - for tc in test_cases { - base, ext := os.split_filename(tc.filename) - testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext) - } -} - -@(test) -test_split_filename_all :: proc(t: ^testing.T) { - Test_Case :: struct { - filename: string, - base, ext: string, - } - - test_cases := [?]Test_Case { - {"", "", ""}, - {"a", "a", ""}, - {".", ".", ""}, - {".a", ".a", ""}, - {".foo", ".foo", ""}, - {".foo.txt", ".foo", "txt"}, - {"a.b", "a", "b"}, - {"foo", "foo", ""}, - {"readme.txt", "readme", "txt"}, - {"pkg.tar.gz", "pkg", "tar.gz"}, - // Assert API ignores directory hierarchies: - {"dir/FILE.TXT", "dir/FILE", "TXT"}, - } - - for tc in test_cases { - base, ext := os.split_filename_all(tc.filename) - testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename_all(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext) - } -} - -@(test) -test_join_filename :: proc(t: ^testing.T) { - Test_Case :: struct { - base, ext: string, - expected: string, - } - - test_cases := [?]Test_Case { - {"", "", ""}, - {"", "foo", "foo"}, - {"foo", "", "foo"}, - {"readme", "txt", "readme.txt"}, - {"pkg.tar", "gz", "pkg.tar.gz"}, - {"pkg", "tar.gz", "pkg.tar.gz"}, - // Assert API ignores directory hierarchies: - {"dir/FILE", "TXT", "dir/FILE.TXT"}, - } - - for tc in test_cases { - result, err := os.join_filename(tc.base, tc.ext, context.temp_allocator) - testing.expectf(t, result == tc.expected && err == nil, "expected join_filename(%q, %q) -> %q; got: %q, %v", tc.base, tc.ext, tc.expected, result, err) - } -} - -Glob_Test :: struct { - pattern: string, - matches: []string, - err: os.Error, -} - -glob_tests := []Glob_Test{ - { - pattern = ODIN_ROOT + "tests/core/os/*/*.txt", - 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 { - globbed, err := os.glob(glob.pattern, context.allocator) - defer { - for file in globbed { - delete(file) - } - delete(globbed) - } - 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 diff --git a/tests/core/os/os2/process.odin b/tests/core/os/os2/process.odin deleted file mode 100644 index c530b4c79..000000000 --- a/tests/core/os/os2/process.odin +++ /dev/null @@ -1,26 +0,0 @@ -#+build !windows -package tests_core_os_os2 - -import os "core:os/os2" -import "core:log" -import "core:testing" - -@(test) -test_process_exec :: proc(t: ^testing.T) { - state, stdout, stderr, err := os.process_exec({ - command = {"echo", "hellope"}, - }, context.allocator) - defer delete(stdout) - defer delete(stderr) - - if err == .Unsupported { - log.warn("process_exec unsupported") - return - } - - testing.expect_value(t, state.exited, true) - testing.expect_value(t, state.success, true) - testing.expect_value(t, err, nil) - testing.expect_value(t, string(stdout), "hellope\n") - testing.expect_value(t, string(stderr), "") -} diff --git a/tests/core/os/path.odin b/tests/core/os/path.odin new file mode 100644 index 000000000..cdfaed56f --- /dev/null +++ b/tests/core/os/path.odin @@ -0,0 +1,562 @@ +package tests_core_os + +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) { + path, err := os.get_executable_path(context.allocator) + defer delete(path) + + log.infof("executable path: %q", path) + + // NOTE: some sanity checks that should always be the case, at least in the CI. + + testing.expect_value(t, err, nil) + testing.expect(t, len(path) > 0) + testing.expect(t, os.is_absolute_path(path)) + _, filename := os.split_path(os.args[0]) + testing.expectf(t, strings.contains(path, filename), "expected the executable path to contain the base of os.args[0] which is %q", filename) +} + +posix_to_dos_path :: proc(path: string) -> string { + if len(path) == 0 { + return path + } + path := path + path, _ = strings.replace_all(path, `/`, `\`, context.temp_allocator) + if path[0] == '\\' { + path = strings.concatenate({"C:", path}, context.temp_allocator) + } + return path +} + +@(test) +test_clean_path :: proc(t: ^testing.T) { + Test_Case :: struct{ + path: string, + expected: string, + } + + when ODIN_OS == .Windows { + test_cases := [?]Test_Case { + {`W:/odin\examples\demo/demo.odin`, `W:\odin\examples\demo\demo.odin`}, + {`\\server\share\path\file.ext`, `\\server\share\path\file.ext`}, + {`//server\share/path\file.ext`, `\\server\share\path\file.ext`}, + {`/\192.168.0.10\share/path\file.ext`, `\\192.168.0.10\share\path\file.ext`}, + {`\\?\C:/Users/Foo/path\file.ext`, `\\?\C:\Users\Foo\path\file.ext`}, + {`\\?\\localhost\share\file.ext`, `\\?\\localhost\share\file.ext`}, + {`//?\/192.168.0.10\share\file.ext`, `\\?\\192.168.0.10\share\file.ext`}, + {`\\.\PhysicalDrive3`, `\\.\PhysicalDrive3`}, + {`/\./PhysicalDrive3`, `\\.\PhysicalDrive3`}, + {`C:\a\..\..`, `C:\`}, + {`C:\a\..`, `C:\`}, + {`C:\あ/a/..`, `C:\あ`}, + {`C:\あ/a/../あ`, `C:\あ\あ`}, + } + } else { + test_cases := [?]Test_Case { + {`../../foo/../../`, `../../..`}, + {`../../foo/..`, `../..`}, + {`../../foo`, `../../foo`}, + {`../..`, `../..`}, + {`.././foo`, `../foo`}, + {`..`, `..`}, + {`.`, `.`}, + {`.foo`, `.foo`}, + {`/../../foo/../../`, `/`}, + {`/../`, `/`}, + {`/..`, `/`}, + {`/`, `/`}, + {`//home/foo/bar/../../`, `/home`}, + {`/a/../..`, `/`}, + {`/a/../`, `/`}, + {`/a/あ`, `/a/あ`}, + {`/a/あ/..`, `/a`}, + {`/あ/a/..`, `/あ`}, + {`/あ/a/../あ`, `/あ/あ`}, + {`/home/../`, `/`}, + {`/home/..`, `/`}, + {`/home/foo/../../usr`, `/usr`}, + {`/home/foo/../..`, `/`}, + {`/home/foo/../`, `/home`}, + {``, `.`}, + {`a/..`, `.`}, + {`a`, `a`}, + {`abc//.//../foo`, `foo`}, + {`foo`, `foo`}, + {`home/foo/bar/../../`, `home`}, + } + } + + for tc in test_cases { + joined, err := os.clean_path(tc.path, context.temp_allocator) + testing.expectf(t, joined == tc.expected && err == nil, "expected clean_path(%q) -> %q; got: %q, %v", tc.path, tc.expected, joined, err) + } +} + +@(test) +test_is_absolute_path :: proc(t: ^testing.T) { + when ODIN_OS == .Windows { + testing.expect(t, os.is_absolute_path(`C:\Windows`)) + } else { + testing.expect(t, os.is_absolute_path("/home")) + } + testing.expect(t, !os.is_absolute_path("home")) +} + +@(test) +test_get_relative_path :: proc(t: ^testing.T) { + Test_Case :: struct { + base, target: string, + expected: string, + } + + Fail_Case :: struct { + base, target: string, + } + + test_cases := [?]Test_Case { + {"", "foo", "foo"}, + {".", "foo", "foo"}, + {"/", "/", "."}, + {"/", "/home/alice/bert", "home/alice/bert"}, + {"/a", "/b", "../b"}, + {"/あ", "/あ/a", "a"}, + {"/a", "/a/あ", "あ"}, + {"/あ", "/い", "../い"}, + {"/a", "/usr", "../usr"}, + {"/home", "/", ".."}, + {"/home", "/home/alice/bert", "alice/bert"}, + {"/home/foo", "/", "../.."}, + {"/home/foo", "/home", ".."}, + {"/home/foo", "/home/alice/bert", "../alice/bert"}, + {"/home/foo", "/home/foo", "."}, + {"/home/foo", "/home/foo/bar", "bar"}, + {"/home/foo/bar", "/home", "../.."}, + {"/home/foo/bar", "/home/alice/bert", "../../alice/bert"}, + {"/home/foo/bar/bert", "/home/alice/bert", "../../../alice/bert"}, + {"/www", "/mount", "../mount"}, + {"foo", ".", ".."}, + {"foo", "bar", "../bar"}, + {"foo", "bar", "../bar"}, + {"foo", "../bar", "../../bar"}, + {"foo", "foo", "."}, + {"foo", "foo/bar", "bar"}, + {"home/foo/bar", "home/alice/bert", "../../alice/bert"}, + } + + fail_cases := [?]Fail_Case { + {"", "/home"}, + {"/home", ""}, + {"..", ""}, + } + + when ODIN_OS == .Windows { + for &tc in test_cases { + tc.base = posix_to_dos_path(tc.base) + tc.target = posix_to_dos_path(tc.target) + // Make one part all capitals to test case-insensitivity. + tc.target = strings.to_upper(tc.target, context.temp_allocator) + tc.expected = posix_to_dos_path(tc.expected) + } + for &tc in fail_cases { + tc.base = posix_to_dos_path(tc.base) + tc.target = posix_to_dos_path(tc.target) + } + } + + for tc in test_cases { + result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator) + joined, err2 := os.join_path({tc.base, result}, context.temp_allocator) + + when ODIN_OS == .Windows { + passed := strings.equal_fold(result, tc.expected) && err == nil + join_guaranteed := strings.equal_fold(joined, tc.target) && err2 == nil + } else { + passed := result == tc.expected && err == nil + join_guaranteed := joined == tc.target && err2 == nil + } + testing.expectf(t, passed, "expected get_relative_path(%q, %q) -> %q; got %q, %v", tc.base, tc.target, tc.expected, result, err) + testing.expectf(t, join_guaranteed, "join_path({{%q, %q}}) guarantee of get_relative_path(%q, %q) failed; got %q, %v instead", tc.base, result, tc.base, tc.target, joined, err2) + } + + for tc in fail_cases { + result, err := os.get_relative_path(tc.base, tc.target, context.temp_allocator) + testing.expectf(t, result == "" && err != nil, "expected get_relative_path(%q, %q) to fail, got %q, %v", tc.base, tc.target, result, err) + } +} + +@(test) +test_split_path :: proc(t: ^testing.T) { + Test_Case :: struct { + path: string, + dir, filename: string, + } + + test_cases := [?]Test_Case { + { "", "", "" }, + { "/", "/", "" }, + { "/a", "/", "a" }, + { "readme.txt", "", "readme.txt" }, + { "/readme.txt", "/", "readme.txt" }, + { "/var/readme.txt", "/var", "readme.txt" }, + { "/home/foo/bar.tar.gz", "/home/foo", "bar.tar.gz" }, + } + + when ODIN_OS == .Windows { + for &tc in test_cases { + tc.path = posix_to_dos_path(tc.path) + tc.dir = posix_to_dos_path(tc.dir) + tc.filename = posix_to_dos_path(tc.filename) + } + } + + for tc in test_cases { + dir, filename := os.split_path(tc.path) + testing.expectf(t, dir == tc.dir && filename == tc.filename, "expected split_path(%q) -> %q, %q; got: %q, %q", tc.path, tc.dir, tc.filename, dir, filename) + } +} + +@(test) +test_join_path :: proc(t: ^testing.T) { + Test_Case :: struct { + elems: []string, + expected: string, + } + + test_cases := [?]Test_Case { + { {"" }, "" }, + { {"/" }, "/" }, + { {"home" }, "home" }, + { {"home", "" }, "home" }, + { {"/home", "" }, "/home" }, + { {"", "home" }, "home" }, + { {"", "/home" }, "/home" }, + { {"", "/home", "", "foo" }, "/home/foo" }, + { {"", "home", "", "", "foo", "" }, "home/foo" }, + } + + when ODIN_OS == .Windows { + for &tc in test_cases { + for &elem in tc.elems { + elem = posix_to_dos_path(elem) + } + tc.expected = posix_to_dos_path(tc.expected) + } + } + + for tc in test_cases { + result, err := os.join_path(tc.elems, context.temp_allocator) + testing.expectf(t, result == tc.expected && err == nil, "expected join_path(%v) -> %q; got: %q, %v", tc.elems, tc.expected, result, err) + } +} + +@(test) +test_split_filename :: proc(t: ^testing.T) { + Test_Case :: struct { + filename: string, + base, ext: string, + } + + test_cases := [?]Test_Case { + {"", "", ""}, + {"a", "a", ""}, + {".", ".", ""}, + {".a", ".a", ""}, + {".foo", ".foo", ""}, + {".foo.txt", ".foo", "txt"}, + {"a.b", "a", "b"}, + {"foo", "foo", ""}, + {"readme.txt", "readme", "txt"}, + {"pkg.tar.gz", "pkg.tar", "gz"}, + // Assert API ignores directory hierarchies: + {"dir/FILE.TXT", "dir/FILE", "TXT"}, + } + + for tc in test_cases { + base, ext := os.split_filename(tc.filename) + testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext) + } +} + +@(test) +test_split_filename_all :: proc(t: ^testing.T) { + Test_Case :: struct { + filename: string, + base, ext: string, + } + + test_cases := [?]Test_Case { + {"", "", ""}, + {"a", "a", ""}, + {".", ".", ""}, + {".a", ".a", ""}, + {".foo", ".foo", ""}, + {".foo.txt", ".foo", "txt"}, + {"a.b", "a", "b"}, + {"foo", "foo", ""}, + {"readme.txt", "readme", "txt"}, + {"pkg.tar.gz", "pkg", "tar.gz"}, + // Assert API ignores directory hierarchies: + {"dir/FILE.TXT", "dir/FILE", "TXT"}, + } + + for tc in test_cases { + base, ext := os.split_filename_all(tc.filename) + testing.expectf(t, base == tc.base && ext == tc.ext, "expected split_filename_all(%q) -> %q, %q; got: %q, %q", tc.filename, tc.base, tc.ext, base, ext) + } +} + +@(test) +test_join_filename :: proc(t: ^testing.T) { + Test_Case :: struct { + base, ext: string, + expected: string, + } + + test_cases := [?]Test_Case { + {"", "", ""}, + {"", "foo", "foo"}, + {"foo", "", "foo"}, + {"readme", "txt", "readme.txt"}, + {"pkg.tar", "gz", "pkg.tar.gz"}, + {"pkg", "tar.gz", "pkg.tar.gz"}, + // Assert API ignores directory hierarchies: + {"dir/FILE", "TXT", "dir/FILE.TXT"}, + } + + for tc in test_cases { + result, err := os.join_filename(tc.base, tc.ext, context.temp_allocator) + testing.expectf(t, result == tc.expected && err == nil, "expected join_filename(%q, %q) -> %q; got: %q, %v", tc.base, tc.ext, tc.expected, result, err) + } +} + +Glob_Test :: struct { + pattern: string, + matches: []string, + err: os.Error, +} + +glob_tests := []Glob_Test{ + { + pattern = ODIN_ROOT + "tests/core/os/*/*.txt", + matches = { + ODIN_ROOT + "tests/core/os/dir/b.txt", + }, + err = {}, + }, + { + pattern = ODIN_ROOT + "tests/core/os/*.odin", + matches = { + 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 = {}, + }, +} + +@(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 { + globbed, err := os.glob(glob.pattern, context.allocator) + defer { + for file in globbed { + delete(file) + } + delete(globbed) + } + 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 diff --git a/tests/core/os/process.odin b/tests/core/os/process.odin new file mode 100644 index 000000000..adb65e95f --- /dev/null +++ b/tests/core/os/process.odin @@ -0,0 +1,26 @@ +#+build !windows +package tests_core_os + +import "core:os" +import "core:log" +import "core:testing" + +@(test) +test_process_exec :: proc(t: ^testing.T) { + state, stdout, stderr, err := os.process_exec({ + command = {"echo", "hellope"}, + }, context.allocator) + defer delete(stdout) + defer delete(stderr) + + if err == .Unsupported { + log.warn("process_exec unsupported") + return + } + + testing.expect_value(t, state.exited, true) + testing.expect_value(t, state.success, true) + testing.expect_value(t, err, nil) + testing.expect_value(t, string(stdout), "hellope\n") + testing.expect_value(t, string(stderr), "") +} 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 -- cgit v1.2.3 From 8366e7094a976b9d7646e3f6eada7e5e1820f117 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 16:27:53 +0100 Subject: 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, -- cgit v1.2.3 From 7a7127aa13e86c29f2783e79732332edad8c514e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 16:34:24 +0100 Subject: 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" -- cgit v1.2.3 From ec05bb57dcde41428b27003dec674dcce87446af Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 18:16:16 +0100 Subject: 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") -- cgit v1.2.3 From fa44c64c42c6f627f27c9b42cf2d79061d483946 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 18:29:31 +0100 Subject: != 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)) } -- cgit v1.2.3 From 454ad1f4b82abd781e44bf10e149d8f1ea468a79 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 19:01:43 +0100 Subject: 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 -- cgit v1.2.3 From 1a37f4eb0cc792783784fca216c79e3f02a3234e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 9 Feb 2026 19:13:32 +0100 Subject: 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)) } } -- cgit v1.2.3 From 264029b2d6a02fe928aff1247f96dc1b55e3d35a Mon Sep 17 00:00:00 2001 From: Shane Shrybman Date: Tue, 10 Feb 2026 21:13:33 -0500 Subject: Remove core:mem dependency from core:container/xar --- core/container/xar/xar.odin | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/container/xar/xar.odin b/core/container/xar/xar.odin index 556f93ee5..b2f1a5499 100644 --- a/core/container/xar/xar.odin +++ b/core/container/xar/xar.odin @@ -26,7 +26,6 @@ */ package container_xar -@(require) import "core:mem" @(require) import "base:intrinsics" @(require) import "base:runtime" @@ -73,7 +72,7 @@ MAX_SHIFT :: PLATFORM_BITS>>1 Array :: struct($T: typeid, $SHIFT: uint) where 0 < SHIFT, SHIFT <= MAX_SHIFT { chunks: [(1 << (_LOG2_PLATFORM_BITS - intrinsics.constant_log2(SHIFT))) + 1][^]T, len: int, - allocator: mem.Allocator, + allocator: runtime.Allocator, } @@ -99,7 +98,7 @@ destroy :: proc(x: ^$X/Array($T, $SHIFT)) { if c != nil { n := 1 << (SHIFT + uint(i if i > 0 else 1) - 1) size_in_bytes := n * size_of(T) - mem.free_with_size(c, size_in_bytes, x.allocator) + runtime.mem_free_with_size(c, size_in_bytes, x.allocator) } } x^ = {} @@ -257,7 +256,7 @@ Example: fmt.println(xar.get(&x, 1)) // world } */ -push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { +push_back_elem :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) { if x.allocator.procedure == nil { // to minic `[dynamic]T` behaviour x.allocator = context.allocator @@ -284,7 +283,7 @@ Append multiple elements to the end of the exponential array. - number of elements successfully added - allocation error if chunk allocation failed (partial append possible) */ -push_back_elems :: proc(x: ^$X/Array($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: mem.Allocator_Error) { +push_back_elems :: proc(x: ^$X/Array($T, $SHIFT), values: ..T, loc := #caller_location) -> (n: int, err: runtime.Allocator_Error) { for value in values { n += push_back_elem(x, value, loc) or_return } @@ -318,7 +317,7 @@ Example: } */ @(require_results) -push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: mem.Allocator_Error) { +push_back_elem_and_get_ptr :: proc(x: ^$X/Array($T, $SHIFT), value: T, loc := #caller_location) -> (ptr: ^T, err: runtime.Allocator_Error) { if x.allocator.procedure == nil { // to minic `[dynamic]T` behaviour x.allocator = context.allocator -- cgit v1.2.3 From bd3cf2a1e6430e916bc9721296a2b18675e89dab Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 Feb 2026 16:33:52 +0100 Subject: Remove -show-import-graph header This allows you to pipe the output to a file and have a working graph without any editing. (Provided you don't also use additional -flags like `-show-timings`.) --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 1fb0ead09..bba9ffe13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2080,7 +2080,6 @@ gb_internal void show_defineables(Checker *c) { gb_internal void show_import_graph(Checker *c) { Parser *p = c->parser; - gb_printf("\nDOT Import graph:\n\n"); gb_printf("digraph odin_import_graph {\n\tnode [shape=box];\n"); int cluster_counter = 0; -- cgit v1.2.3 From 9c47ca73063fa0e335f39d52774b2bbf28e6c6b7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 11 Feb 2026 19:00:11 +0100 Subject: checkout with LFS binaries in nightly CI --- .github/workflows/nightly.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 48ea93ded..c7858c82c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -12,6 +12,8 @@ jobs: runs-on: windows-2022 steps: - uses: actions/checkout@v4 + with: + lfs: true - name: build Odin shell: cmd run: | @@ -47,6 +49,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + lfs: true - name: (Linux) Download LLVM and Build Odin run: | docker run --rm -v "$PWD:/src" -w /src alpine sh -c ' @@ -87,6 +91,8 @@ jobs: runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 + with: + lfs: true - name: (Linux ARM) Download LLVM and Build Odin run: | docker run --rm -v "$PWD:/src" -w /src arm64v8/alpine sh -c ' @@ -127,6 +133,8 @@ jobs: runs-on: macos-15-intel steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Download LLVM and setup PATH run: | brew update @@ -166,6 +174,8 @@ jobs: runs-on: macos-latest # ARM machine steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Download LLVM and setup PATH run: | brew update -- cgit v1.2.3 From 8468d269dcd3166861dc49f50b0e42a0dce4b61e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 Feb 2026 19:15:32 +0100 Subject: Remove @(require_results) from `os.read_directory_iterator` --- .gitignore | 3 +++ core/os/dir.odin | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4efb5d73e..00dbcc2a5 100644 --- a/.gitignore +++ b/.gitignore @@ -279,6 +279,9 @@ demo.bin libLLVM*.so* *.a +# WASM +*.wasm + # shared collection shared/ diff --git a/core/os/dir.odin b/core/os/dir.odin index a2fba81e4..2548480c2 100644 --- a/core/os/dir.odin +++ b/core/os/dir.odin @@ -195,7 +195,6 @@ Example: } } */ -@(require_results) read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { if it.f == nil { return -- cgit v1.2.3 From f755839877b2c565f8087d22c13e475b145829c3 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 11 Feb 2026 20:02:42 +0100 Subject: fix wrong allocator in `endpoint_to_string` Fixes #6251 --- core/net/addr.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index d29b46b65..b75a10562 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -608,13 +608,13 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> return strings.to_string(b) } -// Returns a temporarily-allocated string representation of the endpoint. +// Returns a temporarily-allocated string representation of the endpoint (if allocator isn't overwritten). // If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively. endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string { if ep.port == 0 { return address_to_string(ep.address, allocator) } else { - s := address_to_string(ep.address, context.temp_allocator) + s := address_to_string(ep.address, allocator) b := strings.builder_make(allocator) switch a in ep.address { case IP4_Address: -- cgit v1.2.3 From d49ab07d35d59aef5be659be4e9e0d80dbff1e41 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 11 Feb 2026 20:05:45 +0100 Subject: fix typo --- src/check_stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 66283d3da..bae95b7c7 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1930,7 +1930,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (rs->vals.count < max_val_count) { TokenPos start = ast_token(rs->vals[0]).pos; TokenPos end = ast_end_pos(rs->vals[rs->vals.count-1]); - error_range(start, end, "Expected a %td identifier%s, got %td", max_val_count, max_val_count == 1 ? "" : "s", rs->vals.count); + error_range(start, end, "Expected %td identifier%s, got %td", max_val_count, max_val_count == 1 ? "" : "s", rs->vals.count); } } } -- cgit v1.2.3 From ea6867ed613433640523333146d4a77d23059cf7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 11 Feb 2026 20:11:35 +0100 Subject: Revert "fix wrong allocator in `endpoint_to_string`" This reverts commit f755839877b2c565f8087d22c13e475b145829c3. --- core/net/addr.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index b75a10562..d29b46b65 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -608,13 +608,13 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> return strings.to_string(b) } -// Returns a temporarily-allocated string representation of the endpoint (if allocator isn't overwritten). +// Returns a temporarily-allocated string representation of the endpoint. // If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively. endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string { if ep.port == 0 { return address_to_string(ep.address, allocator) } else { - s := address_to_string(ep.address, allocator) + s := address_to_string(ep.address, context.temp_allocator) b := strings.builder_make(allocator) switch a in ep.address { case IP4_Address: -- cgit v1.2.3 From 43f4b2187cc362a3abba71efc73d05a085c57f04 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 Feb 2026 21:21:48 +0100 Subject: Fix #6251 Introduces `strings.Builder` versions of the various `to_string` procedures. These are used using a stack buffer for `address_to_string` as called by `endpoint_to_string`, requiring no temp allocation for the address that's then written into the endpoint's builder. --- core/net/addr.odin | 122 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 46 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index d29b46b65..888744841 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -459,38 +459,63 @@ split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok return } -// Joins an address or hostname with a port. -join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string { +// Joins an address or hostname with a port, allocated using an Allocator. +join_port_allocator :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string { addr_or_host, _, ok := split_port(address_or_host) if !ok { return addr_or_host } b := strings.builder_make(allocator) - addr := parse_address(addr_or_host) if addr == nil { // hostname strings.write_string(&b, addr_or_host) strings.write_string(&b, ":") strings.write_int(&b, port) + return strings.to_string(b) } else { - switch _ in addr { - case IP4_Address: - strings.write_string(&b, address_to_string(addr)) - strings.write_string(&b, ":") - strings.write_int(&b, port) - case IP6_Address: - strings.write_string(&b, "[") - strings.write_string(&b, address_to_string(addr)) - strings.write_string(&b, "]:") - strings.write_int(&b, port) - } + return _join_port_internal(addr, port, &b) } - return strings.to_string(b) } +// Joins an address or hostname with a port, allocated using a `strings.Builder`. +join_port_builder :: proc(address_or_host: string, port: int, b: ^strings.Builder) -> string { + addr_or_host, _, ok := split_port(address_or_host) + if !ok { + return addr_or_host + } + + addr := parse_address(addr_or_host) + if addr == nil { + // hostname + strings.write_string(b, addr_or_host) + strings.write_string(b, ":") + strings.write_int(b, port) + return strings.to_string(b^) + } else { + return _join_port_internal(addr, port, b) + } +} +join_port :: proc{join_port_allocator, join_port_builder} + +// Also used in `endpoint_to_string_builder`. +@(private) +_join_port_internal :: proc(addr: Address, port: int, b: ^strings.Builder) -> string { + switch a in addr { + case IP4_Address: + address_to_string_builder(addr, b) + strings.write_string(b, ":") + strings.write_int(b, port) + case IP6_Address: + strings.write_string(b, "[") + address_to_string_builder(addr, b) + strings.write_string(b, "]:") + strings.write_int(b, port) + } + return strings.to_string(b^) +} // TODO(tetra): Do we need this? map_to_ip6 :: proc(addr: Address) -> Address { @@ -510,17 +535,26 @@ map_to_ip6 :: proc(addr: Address) -> Address { See RFC 5952 section 4 for IPv6 representation recommendations. */ -address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string { +address_to_string_allocator :: proc(addr: Address, allocator := context.temp_allocator) -> string { b := strings.builder_make(allocator) + return address_to_string_builder(addr, &b) +} + +/* + Returns a string representation of the address using a `strings.Builder`. + + See RFC 5952 section 4 for IPv6 representation recommendations. +*/ +address_to_string_builder :: proc(addr: Address, b: ^strings.Builder) -> string { switch v in addr { case IP4_Address: - strings.write_uint(&b, uint(v[0])) - strings.write_byte(&b, '.') - strings.write_uint(&b, uint(v[1])) - strings.write_byte(&b, '.') - strings.write_uint(&b, uint(v[2])) - strings.write_byte(&b, '.') - strings.write_uint(&b, uint(v[3])) + strings.write_uint(b, uint(v[0])) + strings.write_byte(b, '.') + strings.write_uint(b, uint(v[1])) + strings.write_byte(b, '.') + strings.write_uint(b, uint(v[2])) + strings.write_byte(b, '.') + strings.write_uint(b, uint(v[3])) case IP6_Address: // First find the longest run of zeroes. Zero_Run :: struct { @@ -574,7 +608,7 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> for val, i in v { if best.start == i || best.end == i { // For the left and right side of the best zero run, print a `:`. - strings.write_string(&b, ":") + strings.write_string(b, ":") } else if i < best.start { /* If we haven't made it to the best run yet, print the digit. @@ -584,10 +618,10 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> buf: [32]byte str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {}) - strings.write_string(&b, str) + strings.write_string(b, str) if i < best.start - 1 { - strings.write_string(&b, ":") + strings.write_string(b, ":") } } else if i > best.end { /* @@ -597,42 +631,38 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> buf: [32]byte str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {}) - strings.write_string(&b, str) + strings.write_string(b, str) if i != 7 { - strings.write_string(&b, ":") + strings.write_string(b, ":") } } } } - return strings.to_string(b) + return strings.to_string(b^) } +address_to_string :: proc{address_to_string_allocator, address_to_string_builder} // Returns a temporarily-allocated string representation of the endpoint. // If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively. -endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string { +endpoint_to_string_allocator :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string { + b := strings.builder_make(allocator) + return endpoint_to_string_builder(ep, &b) +} + +// Returns a string representation of the endpoint using a `strings.Builder`. +// If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively. +endpoint_to_string_builder :: proc(ep: Endpoint, b: ^strings.Builder) -> string { if ep.port == 0 { - return address_to_string(ep.address, allocator) + return address_to_string(ep.address, b) } else { - s := address_to_string(ep.address, context.temp_allocator) - b := strings.builder_make(allocator) - switch a in ep.address { - case IP4_Address: - strings.write_string(&b, s) - strings.write_string(&b, ":") - strings.write_int(&b, ep.port) - case IP6_Address: - strings.write_string(&b, "[") - strings.write_string(&b, s) - strings.write_string(&b, "]:") - strings.write_int(&b, ep.port) - } - return strings.to_string(b) + return _join_port_internal(ep.address, ep.port, b) } } -to_string :: proc{address_to_string, endpoint_to_string} +endpoint_to_string :: proc{endpoint_to_string_allocator, endpoint_to_string_builder} +to_string :: proc{address_to_string_allocator, address_to_string_builder, endpoint_to_string_allocator, endpoint_to_string_builder} family_from_address :: proc(addr: Address) -> Address_Family { switch _ in addr { -- cgit v1.2.3 From b82512b4cfca4f5e357d1510696f6665755df6f9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 Feb 2026 22:15:15 +0100 Subject: Restrict `math.wrap` to floats. --- core/math/math.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/math/math.odin b/core/math/math.odin index 7afb32db6..1ecb1da00 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -418,7 +418,7 @@ remap_clamped :: proc "contextless" (old_value, old_min, old_max, new_min, new_m } @(require_results) -wrap :: proc "contextless" (x, y: $T) -> T where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { +wrap :: proc "contextless" (x, y: $T) -> T where intrinsics.type_is_numeric(T), intrinsics.type_is_float(T), !intrinsics.type_is_array(T) { tmp := mod(x, y) return y + tmp if tmp < 0 else tmp } -- cgit v1.2.3 From b18f75c41c4a78c1e368908ee0bfbba8efa1c494 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 Feb 2026 22:52:24 +0100 Subject: Make `core:flags` file open errors more readable If it fails because the file doesn't exist: [Open_File_Error#2] Unable to open "foo.file". File not found. If it does exist and the flags are simple (.Read, .Write or both), it'll say this: [Open_File_Error#2] Unable to open "foo.file" with perms 0o444 as read-only It it does exist but fails to open for other reasons, it'll print: [Open_File_Error#2] Unable to open "foo.file" with perms 0o444 and flags File_Flags{....} Future work: Translate permissions from octal to `ls -al` format (e.g. `-rwxrwxrwx`). --- core/flags/util.odin | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/core/flags/util.odin b/core/flags/util.odin index 0d18fa196..b5e557cf6 100644 --- a/core/flags/util.odin +++ b/core/flags/util.odin @@ -72,12 +72,37 @@ print_errors :: proc(data_type: typeid, error: Error, program: string, style: Pa 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 and flags %v: %s", - specific_error, - specific_error.errno, - specific_error.perms, - specific_error.flags, - specific_error.filename) + if os.exists(specific_error.filename) { + flags: string + if specific_error.flags == {.Read} { + flags = "read-only" + } else if specific_error.flags == {.Write} { + flags = "write-only" + } else if specific_error.flags == {.Read, .Write} { + flags = "read/write" + } + + if flags != "" { + fmt.wprintfln(stderr, "[%T#%i] Unable to open %q with perms 0o%o as %s", + specific_error, + specific_error.errno, + specific_error.filename, + u16(transmute(u32)specific_error.perms), + flags) + } else { + fmt.wprintfln(stderr, "[%T#%i] Unable to open %q with perms 0o%o and flags %v", + specific_error, + specific_error.errno, + specific_error.filename, + u16(transmute(u32)specific_error.perms), + specific_error.flags) + } + } else { + fmt.wprintfln(stderr, "[%T#%i] Unable to open %q. File not found", + specific_error, + specific_error.errno, + specific_error.filename) + } case Validation_Error: fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message) case Help_Request: -- cgit v1.2.3 From 97683ae0146e6668d56e701c1d54a4e1737e41c8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 12 Feb 2026 10:48:26 +0100 Subject: Simplify --- core/os/path.odin | 4 ++-- tests/core/os/file.odin | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/os/path.odin b/core/os/path.odin index e0353e43d..3c22852bf 100644 --- a/core/os/path.odin +++ b/core/os/path.odin @@ -145,7 +145,7 @@ This will remove duplicate separators and unneeded references to the current or parent directory. */ @(require_results) -clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) { +clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: runtime.Allocator_Error) { if path == "" || path == "." { return strings.clone(".", allocator) } @@ -491,7 +491,7 @@ Join all `elems` with the system's path separator and normalize the result. For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`. */ @(require_results) -join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { +join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: runtime.Allocator_Error) { for e, i in elems { if e != "" { temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) diff --git a/tests/core/os/file.odin b/tests/core/os/file.odin index aed57c26c..99ae5b928 100644 --- a/tests/core/os/file.odin +++ b/tests/core/os/file.odin @@ -5,10 +5,9 @@ import "core:testing" @(test) test_clone :: proc(t: ^testing.T) { - joined, err := os.join_path({#directory, "file.odin"}, context.temp_allocator) - testing.expect_value(t, err, nil) - f: ^os.File - f, err = os.open(joined) + joined, j_err := os.join_path({#directory, "file.odin"}, context.temp_allocator) + testing.expect_value(t, j_err, nil) + f, err := os.open(joined) testing.expect_value(t, err, nil) testing.expect(t, f != nil) -- cgit v1.2.3 From 658e6b9d9fabe0b249f552257fe8918b010003b0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 12 Feb 2026 12:02:11 +0100 Subject: Add `core:log` support for js/wasm Also add #panic when importing `core:os` on wasm. --- core/log/console_logger_js.odin | 119 ++++++++++++++++++++++++++++++++++++++++ core/os/dir_js.odin | 6 +- core/os/env_js.odin | 10 ++-- core/os/errors_js.odin | 6 +- core/os/file_js.odin | 9 ++- core/os/heap_js.odin | 6 +- core/os/path_js.odin | 4 ++ core/os/pipe_js.odin | 6 +- core/os/process_js.odin | 4 ++ core/os/stat_js.odin | 4 ++ core/os/temp_file_js.odin | 4 ++ core/os/wasm.odin | 4 ++ 12 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 core/log/console_logger_js.odin create mode 100644 core/os/wasm.odin diff --git a/core/log/console_logger_js.odin b/core/log/console_logger_js.odin new file mode 100644 index 000000000..1e07b6cdc --- /dev/null +++ b/core/log/console_logger_js.odin @@ -0,0 +1,119 @@ +#+build js +package log + +import "core:fmt" +import "core:strings" +import "core:time" + +Level_Headers := [?]string{ + 0..<10 = "[DEBUG] --- ", + 10..<20 = "[INFO ] --- ", + 20..<30 = "[WARN ] --- ", + 30..<40 = "[ERROR] --- ", + 40..<50 = "[FATAL] --- ", +} + +Default_Console_Logger_Opts :: Options{ + .Level, + .Short_File_Path, + .Line, + .Procedure, +} | Full_Timestamp_Opts + +Console_Logger_Data :: struct { + ident: string, +} + +create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { + data := new(Console_Logger_Data, allocator) + data.ident = strings.clone(ident) + return Logger{console_logger_proc, data, lowest, opt} +} + +destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { + data := cast(^Console_Logger_Data)log.data + delete(data.ident) + free(log.data, allocator) +} + +console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + options := options + data := cast(^Console_Logger_Data)logger_data + + 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[:]) + + if .Level in options { + fmt.sbprint(&buf, Level_Headers[level]) + } + + when time.IS_SUPPORTED { + do_time_header(options, &buf, time.now()) + } + + do_location_header(options, &buf, location) + + if data.ident != "" { + fmt.sbprintf(&buf, "[%s] ", data.ident) + } + + h := fmt.stderr if level >= .Error else fmt.stdout + + //TODO(Hoej): When we have better atomics and such, make this thread-safe + fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) +} + +do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { + when time.IS_SUPPORTED { + if Full_Timestamp_Opts & opts != nil { + fmt.sbprint(buf, "[") + y, m, d := time.date(t) + h, min, s := time.clock(t) + if .Date in opts { + fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d) + if .Time in opts { + fmt.sbprint(buf, " ") + } + } + if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } + fmt.sbprint(buf, "] ") + } + } +} + +do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) { + if Location_Header_Opts & opts == nil { + return + } + fmt.sbprint(buf, "[") + + file := location.file_path + if .Short_File_Path in opts { + last := 0 + for r, i in location.file_path { + if r == '/' { + last = i+1 + } + } + file = location.file_path[last:] + } + + if Location_File_Opts & opts != nil { + fmt.sbprint(buf, file) + } + if .Line in opts { + if Location_File_Opts & opts != nil { + fmt.sbprint(buf, ":") + } + fmt.sbprint(buf, location.line) + } + + if .Procedure in opts { + if (Location_File_Opts | {.Line}) & opts != nil { + fmt.sbprint(buf, ":") + } + fmt.sbprintf(buf, "%s()", location.procedure) + } + + fmt.sbprint(buf, "] ") +} \ No newline at end of file diff --git a/core/os/dir_js.odin b/core/os/dir_js.odin index 8c45cf63b..cd6227507 100644 --- a/core/os/dir_js.odin +++ b/core/os/dir_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:intrinsics" Read_Directory_Iterator_Impl :: struct { @@ -21,4 +25,4 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { -} +} \ No newline at end of file diff --git a/core/os/env_js.odin b/core/os/env_js.odin index 644af61bd..55ffce593 100644 --- a/core/os/env_js.odin +++ b/core/os/env_js.odin @@ -2,16 +2,16 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + 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 @@ -39,4 +39,4 @@ _clear_env :: proc() { @(require_results) _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { return {}, .Unsupported -} +} \ No newline at end of file diff --git a/core/os/errors_js.odin b/core/os/errors_js.odin index 34779807d..368b94d68 100644 --- a/core/os/errors_js.odin +++ b/core/os/errors_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + _Platform_Error :: enum i32 {} _error_string :: proc(errno: i32) -> string { @@ -10,4 +14,4 @@ _error_string :: proc(errno: i32) -> string { _get_platform_error :: proc(errno: _Platform_Error) -> Error { return Platform_Error(errno) -} +} \ No newline at end of file diff --git a/core/os/file_js.odin b/core/os/file_js.odin index 5d921c9b0..aef811c11 100644 --- a/core/os/file_js.odin +++ b/core/os/file_js.odin @@ -2,12 +2,17 @@ #+private package os -import "base:runtime" +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. +import "base:runtime" import "core:io" import "core:time" -File_Impl :: distinct rawptr +File_Impl :: struct { + file: File, +} _open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) { return nil, .Unsupported diff --git a/core/os/heap_js.odin b/core/os/heap_js.odin index 5f8d9bd35..ebc8f902d 100644 --- a/core/os/heap_js.odin +++ b/core/os/heap_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:runtime" -_heap_allocator_proc :: runtime.wasm_allocator_proc +_heap_allocator_proc :: runtime.wasm_allocator_proc \ No newline at end of file diff --git a/core/os/path_js.odin b/core/os/path_js.odin index 6ac845560..874b45c89 100644 --- a/core/os/path_js.odin +++ b/core/os/path_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:runtime" _Path_Separator :: '/' diff --git a/core/os/pipe_js.odin b/core/os/pipe_js.odin index aea5e9a83..5e724ea00 100644 --- a/core/os/pipe_js.odin +++ b/core/os/pipe_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + _pipe :: proc() -> (r, w: ^File, err: Error) { err = .Unsupported return @@ -11,4 +15,4 @@ _pipe :: proc() -> (r, w: ^File, err: Error) { _pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { err = .Unsupported return -} +} \ No newline at end of file diff --git a/core/os/process_js.odin b/core/os/process_js.odin index 6283d270c..9b682da0c 100644 --- a/core/os/process_js.odin +++ b/core/os/process_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:runtime" import "core:time" diff --git a/core/os/stat_js.odin b/core/os/stat_js.odin index bad75486b..a304435d9 100644 --- a/core/os/stat_js.odin +++ b/core/os/stat_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:runtime" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { diff --git a/core/os/temp_file_js.odin b/core/os/temp_file_js.odin index 32ce1c484..53158d843 100644 --- a/core/os/temp_file_js.odin +++ b/core/os/temp_file_js.odin @@ -2,6 +2,10 @@ #+private package os +// None of this does anything on js/wasm. +// It's only here so importing `core:os` on wasm panics cleanly, +// without spamming about all sorts of missing procs and types. + import "base:runtime" _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { diff --git a/core/os/wasm.odin b/core/os/wasm.odin new file mode 100644 index 000000000..5153a3f93 --- /dev/null +++ b/core/os/wasm.odin @@ -0,0 +1,4 @@ +#+build wasm32, wasm64p32 +package os + +#panic("core:os is unsupported on js/wasm") \ No newline at end of file -- cgit v1.2.3 From 62cfa9e8e35ddc0c175ac5c1b429108c19b60b71 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 12 Feb 2026 12:21:01 +0100 Subject: Narrowly tailor panic. --- core/os/wasm.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/wasm.odin b/core/os/wasm.odin index 5153a3f93..79c00f694 100644 --- a/core/os/wasm.odin +++ b/core/os/wasm.odin @@ -1,4 +1,4 @@ -#+build wasm32, wasm64p32 +#+build js wasm32, js wasm64p32 package os #panic("core:os is unsupported on js/wasm") \ No newline at end of file -- cgit v1.2.3 From 1159110e735ba84d651f4bbc4e9883fd83e9eddc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 12 Feb 2026 13:55:17 +0100 Subject: Fix #6265 --- core/path/filepath/path.odin | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 58dc06103..a325ce4cd 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -251,15 +251,22 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> ( */ dir :: proc(path: string, allocator := context.allocator) -> string { context.allocator = allocator - vol := volume_name(path) + vol_len := len(volume_name(path)) + when ODIN_OS == .Windows { + if len(path) >= 3 && is_separator(path[2]) { + vol_len += 1 + } + } + vol := path[:vol_len] + i := len(path) - 1 - for i >= len(vol) && !is_separator(path[i]) { + for i >= vol_len && !is_separator(path[i]) { i -= 1 } - dir, dir_err := clean(path[len(vol) : i+1], allocator) + dir, dir_err := clean(path[vol_len : i+1], allocator) if dir_err != nil { return "" } defer delete(dir) - if dir == "." && len(vol) > 2 { + if dir == "." && vol_len > 2 { return strings.clone(vol) } return strings.concatenate({vol, dir}) -- cgit v1.2.3