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/example/example.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'core/flags/example') 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") } } -- 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 (limited to 'core/flags/example') 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