mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-09 04:51:37 -07:00
Compare commits
303 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adcaace03c | |||
| f205df1996 | |||
| c59ad24856 | |||
| 2b9b0ac62e | |||
| 67e6f57192 | |||
| 24ddb8506f | |||
| 6ff0cc0b40 | |||
| 7620fe1ac6 | |||
| 22e0f5ecd0 | |||
| fce2042375 | |||
| 57594153a1 | |||
| ff93ea5bf1 | |||
| 4a54676f31 | |||
| 0d900521bc | |||
| bd7ffcc048 | |||
| 2f771bee7b | |||
| ae5214c1f2 | |||
| 84d8798ad3 | |||
| ab7e1e01de | |||
| bbafc3fbd6 | |||
| 194fa7cd98 | |||
| 4c12addcaf | |||
| 0bd27381c3 | |||
| 6dce07790a | |||
| 203ae32b79 | |||
| 3c493194c9 | |||
| 692764aad3 | |||
| 937e5de1d8 | |||
| 7de67f8c1b | |||
| f5d66bcb6f | |||
| bf82c40964 | |||
| aa6299f114 | |||
| 7ffca8ed58 | |||
| 030405dbb6 | |||
| 8862f9118b | |||
| e2e98672bd | |||
| 51f295cacc | |||
| 0c50ac3396 | |||
| 2da81a4a26 | |||
| b6d4853a33 | |||
| 020b147222 | |||
| 34b037f19b | |||
| 88ee5d1a6d | |||
| 0892d84c17 | |||
| 2501d50f9c | |||
| 1e4a4181e2 | |||
| 3e1daa002c | |||
| 4c13dee18f | |||
| 95497626e3 | |||
| 9ada48054f | |||
| 99d6c58971 | |||
| b974b3ccfd | |||
| 75cf45f0be | |||
| d337a11e83 | |||
| a86386d882 | |||
| bbf40bf318 | |||
| b054585066 | |||
| 90c44c34a9 | |||
| e449cc9e2d | |||
| 909ed93cd3 | |||
| 9c97b11ab9 | |||
| 5ae44b25da | |||
| b2ecb37b35 | |||
| 144d034475 | |||
| 50d8dc91cf | |||
| e58915e12f | |||
| 7f8c2a44a4 | |||
| d986eee36b | |||
| b3e712e0b8 | |||
| 05434daa69 | |||
| 2c4a478987 | |||
| a80ca23937 | |||
| ba02ef8f25 | |||
| ef3d8bdc42 | |||
| 951511704d | |||
| 23aae6ab0f | |||
| 3748e117a9 | |||
| 33798b8b80 | |||
| 2e85083d0a | |||
| 23b8a9033a | |||
| 313b6874b1 | |||
| 6004412365 | |||
| adac039a2b | |||
| adcc865c70 | |||
| fe533fb809 | |||
| fa62963da7 | |||
| 1f5bb99548 | |||
| f1cd56c28a | |||
| 852c8b533c | |||
| 582a72574e | |||
| b249ddde48 | |||
| b020ba2b5f | |||
| 03c6862d51 | |||
| b7f953b2ee | |||
| 0b064765c9 | |||
| eb3ddce706 | |||
| 5fdc9fa3b6 | |||
| bfb231fb8a | |||
| 74fb74d9cb | |||
| 97d7e295dd | |||
| 0727e91aeb | |||
| 8dc70f797c | |||
| 2cf8a9da6f | |||
| c1c7128634 | |||
| 0e9ef50e63 | |||
| e05944601a | |||
| 49cf0125a9 | |||
| 0602a16ad6 | |||
| 09a0dad115 | |||
| 243a3f5006 | |||
| 33ca85bd4e | |||
| ca15eb26f0 | |||
| 28eebc14d0 | |||
| 4210aa9ab9 | |||
| 5bbdbadc25 | |||
| 00f24a3249 | |||
| d8a798372b | |||
| 8d5c865814 | |||
| 5134d6bc63 | |||
| ede57720fd | |||
| 830d2007a6 | |||
| 5f3b6c9722 | |||
| 93f7d3bfb9 | |||
| f0ef10aa57 | |||
| bf91fcc6f7 | |||
| 2d894a0164 | |||
| 731b9c902f | |||
| ac0f3c8433 | |||
| 56bfbbf501 | |||
| 63b5d472fa | |||
| 233e3c76fd | |||
| 2334dadb6a | |||
| 6f4f2754d6 | |||
| 30ced04137 | |||
| c39bd7e089 | |||
| 3470d986f0 | |||
| 7c0257fcda | |||
| 9af6d6c9c6 | |||
| 1ecab2fcbc | |||
| a262c0bbf3 | |||
| 7f3f164736 | |||
| 085db569f1 | |||
| 133af6f826 | |||
| 1c2301e2f1 | |||
| ef999f660b | |||
| fad330acd1 | |||
| 8f1af2630d | |||
| eea92a3371 | |||
| ff275df5ea | |||
| 6e56e5457d | |||
| afaa5f2deb | |||
| 0674b1b6ee | |||
| 1ef8602f19 | |||
| ee597fc9b8 | |||
| e254581a1b | |||
| 38ea140b3f | |||
| d939d6079a | |||
| 6e9475d61d | |||
| f6134422e6 | |||
| 5c05038af0 | |||
| 5da5ebff13 | |||
| 798932523e | |||
| 5267a864db | |||
| f02334237a | |||
| d5ea492ef5 | |||
| 96ac405952 | |||
| 38d58e818c | |||
| 2d71ab6f29 | |||
| 090723179b | |||
| 5b55fbff23 | |||
| 64f200dc74 | |||
| 99e2f0c91e | |||
| c02ff3af27 | |||
| 553f338f6f | |||
| bb72c804fb | |||
| 13c6352b8e | |||
| 707c2b3d7a | |||
| 14eed79a21 | |||
| 2ca30e3acd | |||
| caf9716bf1 | |||
| d569daae33 | |||
| 28f7f57247 | |||
| ffc592c7cf | |||
| 3567c006e6 | |||
| b9db450a7d | |||
| 0d65c6dcf7 | |||
| dfee7c103e | |||
| 025fc2685d | |||
| 5b5154eda0 | |||
| ecf65303cd | |||
| 8ea6972496 | |||
| 9afd9f9bea | |||
| c8d3a9121b | |||
| f0950e2286 | |||
| edd78ae129 | |||
| 8738695bd8 | |||
| 76cb3b7874 | |||
| 02a098eb48 | |||
| 1f17a391c6 | |||
| a8fb346fd7 | |||
| 96fb5eb0ba | |||
| 9c7656d59a | |||
| a53bff5645 | |||
| a9182cfd8c | |||
| de6c0f682f | |||
| 9cfcb43725 | |||
| d9b590c76e | |||
| 450c535e9a | |||
| 0dc166e594 | |||
| 8ba080a66d | |||
| 7801582819 | |||
| bc882e678d | |||
| 58e173f279 | |||
| b7d7b9d6b3 | |||
| cf091a48b4 | |||
| d9bfe9e5d4 | |||
| 245a6697ef | |||
| 6226c2978d | |||
| 3d325e52c6 | |||
| 6a6d7701f9 | |||
| 50c688f0f7 | |||
| ef99d03f21 | |||
| af265250c2 | |||
| c6f463b8c9 | |||
| b7d75e2f1d | |||
| 6aa54cbe9a | |||
| 090e30f07b | |||
| f5d507a9b9 | |||
| 8e5e43f335 | |||
| b9f7b2fdfa | |||
| 59a601f2cf | |||
| b6a5c5f5d2 | |||
| a2f02b8b32 | |||
| ee4ed126e1 | |||
| c36dc91849 | |||
| 91dccf8d62 | |||
| 1fc3a25f47 | |||
| 7322b63991 | |||
| f860b09065 | |||
| 45b742be23 | |||
| d325ee4b91 | |||
| 87d6910bb8 | |||
| 9c9300ed58 | |||
| e559cf32fe | |||
| f2202db517 | |||
| fb735883be | |||
| 6a2ef1f4f3 | |||
| 051c9cb564 | |||
| eb60ec3899 | |||
| 233f47cc99 | |||
| c386c72d10 | |||
| 20eacc4a84 | |||
| a28699b42d | |||
| 4d74d5bc99 | |||
| ed371f2b0d | |||
| 66f2881a78 | |||
| 7d4e9497eb | |||
| c08809e29d | |||
| 99460c9e32 | |||
| d86df8321c | |||
| 806f56ca38 | |||
| c40b6c7c2f | |||
| 896b7145b3 | |||
| 8a2a70a3c2 | |||
| 97352538ad | |||
| c6c4ad6188 | |||
| 210f47b8ab | |||
| 94c1331c07 | |||
| d6407e9636 | |||
| df58a00564 | |||
| d546677ae7 | |||
| 04b1023988 | |||
| 9a81071687 | |||
| 48685e8bf1 | |||
| 0f697a0f26 | |||
| 8ddb493b96 | |||
| 039d9938b9 | |||
| f50ea649f6 | |||
| 6e647a88eb | |||
| 986cba584e | |||
| b427a4c8c9 | |||
| 133ee70a5b | |||
| 494612827a | |||
| 1113f23475 | |||
| 8626f58773 | |||
| 7032867421 | |||
| e6239ca3c2 | |||
| 162628000f | |||
| 55b79c078c | |||
| 570b127869 | |||
| 6179d4feb1 | |||
| 2ff5d016d5 | |||
| 854a95327a | |||
| 8a16fd7699 | |||
| 7bbcf22deb | |||
| 0324281634 | |||
| de0a3e0ab9 | |||
| d26110da7f | |||
| 60e73d91f6 | |||
| 5eeb436626 | |||
| 802333e454 | |||
| eb457d688d | |||
| 34cb558279 |
@@ -163,6 +163,13 @@ jobs:
|
|||||||
cd tests\internal
|
cd tests\internal
|
||||||
call build.bat
|
call build.bat
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
- name: Odin documentation tests
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||||
|
cd tests\documentation
|
||||||
|
call build.bat
|
||||||
|
timeout-minutes: 10
|
||||||
- name: core:math/big tests
|
- name: core:math/big tests
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -94,7 +94,15 @@ cap :: proc(array: Array_Type) -> int ---
|
|||||||
|
|
||||||
size_of :: proc($T: typeid) -> int ---
|
size_of :: proc($T: typeid) -> int ---
|
||||||
align_of :: proc($T: typeid) -> int ---
|
align_of :: proc($T: typeid) -> int ---
|
||||||
offset_of :: proc($T: typeid) -> uintptr ---
|
|
||||||
|
// e.g. offset_of(t.f), where t is an instance of the type T
|
||||||
|
offset_of_selector :: proc(selector: $T) -> uintptr ---
|
||||||
|
// e.g. offset_of(T, f), where T can be the type instead of a variable
|
||||||
|
offset_of_member :: proc($T: typeid, member: $M) -> uintptr ---
|
||||||
|
offset_of :: proc{offset_of_selector, offset_of_member}
|
||||||
|
// e.g. offset_of(T, "f"), where T can be the type instead of a variable
|
||||||
|
offset_of_by_string :: proc($T: typeid, member: string) -> uintptr ---
|
||||||
|
|
||||||
type_of :: proc(x: expr) -> type ---
|
type_of :: proc(x: expr) -> type ---
|
||||||
type_info_of :: proc($T: typeid) -> ^runtime.Type_Info ---
|
type_info_of :: proc($T: typeid) -> ^runtime.Type_Info ---
|
||||||
typeid_of :: proc($T: typeid) -> typeid ---
|
typeid_of :: proc($T: typeid) -> typeid ---
|
||||||
@@ -109,7 +117,7 @@ jmag :: proc(value: Quaternion) -> Float ---
|
|||||||
kmag :: proc(value: Quaternion) -> Float ---
|
kmag :: proc(value: Quaternion) -> Float ---
|
||||||
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
|
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
|
||||||
|
|
||||||
expand_to_tuple :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
|
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
|
||||||
|
|
||||||
min :: proc(values: ..T) -> T ---
|
min :: proc(values: ..T) -> T ---
|
||||||
max :: proc(values: ..T) -> T ---
|
max :: proc(values: ..T) -> T ---
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ when ODIN_OS == .Windows {
|
|||||||
@(link_name="_Cnd_destroy") cnd_destroy :: proc(cond: ^cnd_t) ---
|
@(link_name="_Cnd_destroy") cnd_destroy :: proc(cond: ^cnd_t) ---
|
||||||
@(link_name="_Cnd_init") cnd_init :: proc(cond: ^cnd_t) -> int ---
|
@(link_name="_Cnd_init") cnd_init :: proc(cond: ^cnd_t) -> int ---
|
||||||
@(link_name="_Cnd_signal") cnd_signal :: proc(cond: ^cnd_t) -> int ---
|
@(link_name="_Cnd_signal") cnd_signal :: proc(cond: ^cnd_t) -> int ---
|
||||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---
|
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, mtx: ^mtx_t, ts: ^timespec) -> int ---
|
||||||
@(link_name="_Cnd_wait") cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---
|
@(link_name="_Cnd_wait") cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---
|
||||||
|
|
||||||
// 7.26.4 Mutex functions
|
// 7.26.4 Mutex functions
|
||||||
|
|||||||
@@ -212,6 +212,8 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
|
|||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||||
|
// TODO: REMOVE ALL USE OF context.temp_allocator here
|
||||||
|
// the is literally no need for it
|
||||||
b := make([]u8, size, context.temp_allocator)
|
b := make([]u8, size, context.temp_allocator)
|
||||||
_, e := z.input->impl_read(b[:])
|
_, e := z.input->impl_read(b[:])
|
||||||
if e == .None {
|
if e == .None {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ _rand_bytes :: proc (dst: []byte) {
|
|||||||
|
|
||||||
for l > 0 {
|
for l > 0 {
|
||||||
to_read := min(l, _MAX_PER_CALL_BYTES)
|
to_read := min(l, _MAX_PER_CALL_BYTES)
|
||||||
ret := unix.sys_getrandom(raw_data(dst), to_read, 0)
|
ret := unix.sys_getrandom(raw_data(dst), uint(to_read), 0)
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
switch os.Errno(-ret) {
|
switch os.Errno(-ret) {
|
||||||
case os.EINTR:
|
case os.EINTR:
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ package util
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
// Keep vet happy
|
||||||
|
_ :: mem
|
||||||
|
|
||||||
// @note(bp): this can replace the other two
|
// @note(bp): this can replace the other two
|
||||||
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
|
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
|
||||||
|
|
||||||
|
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
|
||||||
|
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
|
||||||
|
*/
|
||||||
|
package dynlib
|
||||||
+81
-2
@@ -1,15 +1,94 @@
|
|||||||
package dynlib
|
package dynlib
|
||||||
|
|
||||||
|
/*
|
||||||
|
A handle to a dynamically loaded library.
|
||||||
|
*/
|
||||||
Library :: distinct rawptr
|
Library :: distinct rawptr
|
||||||
|
|
||||||
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
/*
|
||||||
|
Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
|
||||||
|
library available to resolve references in subsequently loaded libraries.
|
||||||
|
|
||||||
|
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||||
|
On `windows` this paramater is ignored.
|
||||||
|
|
||||||
|
The underlying behaviour is platform specific.
|
||||||
|
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
|
||||||
|
On `windows` refer to `LoadLibraryW`.
|
||||||
|
|
||||||
|
**Implicit Allocators**
|
||||||
|
`context.temp_allocator`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
load_my_library :: proc() {
|
||||||
|
LIBRARY_PATH :: "my_library.dll"
|
||||||
|
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||||
|
if ! ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) {
|
||||||
return _load_library(path, global_symbols)
|
return _load_library(path, global_symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
unload_library :: proc(library: Library) -> bool {
|
/*
|
||||||
|
Unloads a dynamic library.
|
||||||
|
|
||||||
|
The underlying behaviour is platform specific.
|
||||||
|
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
|
||||||
|
On `windows` refer to `FreeLibrary`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
load_then_unload_my_library :: proc() {
|
||||||
|
LIBRARY_PATH :: "my_library.dll"
|
||||||
|
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||||
|
if ! ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
did_unload := dynlib.unload_library(library)
|
||||||
|
if ! did_unload {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
unload_library :: proc(library: Library) -> (did_unload: bool) {
|
||||||
return _unload_library(library)
|
return _unload_library(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loads the address of a procedure/variable from a dynamic library.
|
||||||
|
|
||||||
|
The underlying behaviour is platform specific.
|
||||||
|
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
|
||||||
|
On `windows` refer to `GetProcAddress`.
|
||||||
|
|
||||||
|
**Implicit Allocators**
|
||||||
|
`context.temp_allocator`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
find_a_in_my_library :: proc() {
|
||||||
|
LIBRARY_PATH :: "my_library.dll"
|
||||||
|
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||||
|
if ! ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a, found_a := dynlib.symbol_address(library, "a")
|
||||||
|
if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a)
|
||||||
|
}
|
||||||
|
*/
|
||||||
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
|
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
|
||||||
return _symbol_address(library, symbol)
|
return _symbol_address(library, symbol)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//+build js
|
||||||
|
//+private
|
||||||
|
package dynlib
|
||||||
|
|
||||||
|
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_unload_library :: proc(library: Library) -> bool {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -4,10 +4,12 @@ package dynlib
|
|||||||
|
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||||
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
handle := cast(Library)win32.LoadLibraryW(wide_path)
|
handle := cast(Library)win32.LoadLibraryW(wide_path)
|
||||||
return handle, handle != nil
|
return handle, handle != nil
|
||||||
@@ -19,6 +21,7 @@ _unload_library :: proc(library: Library) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||||
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
||||||
found = ptr != nil
|
found = ptr != nil
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
|||||||
case runtime.Type_Info_Procedure:
|
case runtime.Type_Info_Procedure:
|
||||||
return .Unsupported_Type
|
return .Unsupported_Type
|
||||||
|
|
||||||
case runtime.Type_Info_Tuple:
|
case runtime.Type_Info_Parameters:
|
||||||
return .Unsupported_Type
|
return .Unsupported_Type
|
||||||
|
|
||||||
case runtime.Type_Info_Simd_Vector:
|
case runtime.Type_Info_Simd_Vector:
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ Error :: enum {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
destroy_value :: proc(value: Value) {
|
destroy_value :: proc(value: Value, allocator := context.allocator) {
|
||||||
|
context.allocator = allocator
|
||||||
#partial switch v in value {
|
#partial switch v in value {
|
||||||
case Object:
|
case Object:
|
||||||
for key, elem in v {
|
for key, elem in v {
|
||||||
@@ -104,4 +105,3 @@ destroy_value :: proc(value: Value) {
|
|||||||
delete(v)
|
delete(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -346,6 +346,8 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
|||||||
|
|
||||||
fields := reflect.struct_fields_zipped(ti.id)
|
fields := reflect.struct_fields_zipped(ti.id)
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
field_used := make([]bool, len(fields), context.temp_allocator)
|
field_used := make([]bool, len(fields), context.temp_allocator)
|
||||||
|
|
||||||
use_field_idx := -1
|
use_field_idx := -1
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import "core:intrinsics"
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
likely :: intrinsics.expect
|
likely :: intrinsics.expect
|
||||||
|
|
||||||
@@ -408,7 +409,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
|||||||
next := scan(t)
|
next := scan(t)
|
||||||
#partial switch next.kind {
|
#partial switch next.kind {
|
||||||
case .Ident:
|
case .Ident:
|
||||||
if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
|
if len(next.text) == 3 && strings.equal_fold(next.text, "xml") {
|
||||||
parse_prologue(doc) or_return
|
parse_prologue(doc) or_return
|
||||||
} else if len(doc.prologue) > 0 {
|
} else if len(doc.prologue) > 0 {
|
||||||
/*
|
/*
|
||||||
@@ -614,6 +615,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "encoding":
|
case "encoding":
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
switch strings.to_lower(attr.val, context.temp_allocator) {
|
switch strings.to_lower(attr.val, context.temp_allocator) {
|
||||||
case "utf-8", "utf8":
|
case "utf-8", "utf8":
|
||||||
doc.encoding = .UTF_8
|
doc.encoding = .UTF_8
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ A period with no following number specifies a precision of 0.
|
|||||||
Examples:
|
Examples:
|
||||||
%f default width, default precision
|
%f default width, default precision
|
||||||
%8f width 8, default precision
|
%8f width 8, default precision
|
||||||
%.3f default width, precision 2
|
%.2f default width, precision 2
|
||||||
%8.3f width 8, precision 3
|
%8.3f width 8, precision 3
|
||||||
%8.f width 8, precision 0
|
%8.f width 8, precision 0
|
||||||
|
|
||||||
|
|||||||
+562
-100
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,48 @@
|
|||||||
package image
|
package image
|
||||||
|
|
||||||
import "core:os"
|
import "core:mem"
|
||||||
|
import "core:bytes"
|
||||||
|
|
||||||
|
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||||
|
Destroy_Proc :: #type proc(img: ^Image)
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||||
|
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||||
|
|
||||||
|
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||||
|
assert(loader != nil)
|
||||||
|
assert(destroyer != nil)
|
||||||
|
assert(_internal_loaders[kind] == nil)
|
||||||
|
_internal_loaders[kind] = loader
|
||||||
|
|
||||||
|
assert(_internal_destroyers[kind] == nil)
|
||||||
|
_internal_destroyers[kind] = destroyer
|
||||||
|
}
|
||||||
|
|
||||||
|
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
loader := _internal_loaders[which(data)]
|
||||||
|
if loader == nil {
|
||||||
|
return nil, .Unsupported_Format
|
||||||
|
}
|
||||||
|
return loader(data, options, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||||
|
if img == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context.allocator = allocator
|
||||||
|
destroyer := _internal_destroyers[img.which]
|
||||||
|
if destroyer != nil {
|
||||||
|
destroyer(img)
|
||||||
|
} else {
|
||||||
|
assert(img.metadata == nil)
|
||||||
|
bytes.buffer_destroy(&img.pixels)
|
||||||
|
free(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Which_File_Type :: enum {
|
Which_File_Type :: enum {
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -28,11 +70,6 @@ Which_File_Type :: enum {
|
|||||||
XBM, // X BitMap
|
XBM, // X BitMap
|
||||||
}
|
}
|
||||||
|
|
||||||
which :: proc{
|
|
||||||
which_bytes,
|
|
||||||
which_file,
|
|
||||||
}
|
|
||||||
|
|
||||||
which_bytes :: proc(data: []byte) -> Which_File_Type {
|
which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||||
test_tga :: proc(s: string) -> bool {
|
test_tga :: proc(s: string) -> bool {
|
||||||
get8 :: #force_inline proc(s: ^string) -> u8 {
|
get8 :: #force_inline proc(s: ^string) -> u8 {
|
||||||
@@ -164,16 +201,3 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
|
|||||||
}
|
}
|
||||||
return .Unknown
|
return .Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
which_file :: proc(path: string) -> Which_File_Type {
|
|
||||||
f, err := os.open(path)
|
|
||||||
if err != 0 {
|
|
||||||
return .Unknown
|
|
||||||
}
|
|
||||||
header: [128]byte
|
|
||||||
os.read(f, header[:])
|
|
||||||
file_type := which_bytes(header[:])
|
|
||||||
os.close(f)
|
|
||||||
return file_type
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//+build js
|
||||||
|
package image
|
||||||
|
|
||||||
|
load :: proc{
|
||||||
|
load_from_bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
which :: proc{
|
||||||
|
which_bytes,
|
||||||
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package image
|
|
||||||
|
|
||||||
import "core:mem"
|
|
||||||
import "core:os"
|
|
||||||
import "core:bytes"
|
|
||||||
|
|
||||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
|
||||||
Destroy_Proc :: #type proc(img: ^Image)
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
|
||||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
|
||||||
|
|
||||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
|
||||||
assert(loader != nil)
|
|
||||||
assert(destroyer != nil)
|
|
||||||
assert(_internal_loaders[kind] == nil)
|
|
||||||
_internal_loaders[kind] = loader
|
|
||||||
|
|
||||||
assert(_internal_destroyers[kind] == nil)
|
|
||||||
_internal_destroyers[kind] = destroyer
|
|
||||||
}
|
|
||||||
|
|
||||||
load :: proc{
|
|
||||||
load_from_bytes,
|
|
||||||
load_from_file,
|
|
||||||
}
|
|
||||||
|
|
||||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
loader := _internal_loaders[which(data)]
|
|
||||||
if loader == nil {
|
|
||||||
return nil, .Unsupported_Format
|
|
||||||
}
|
|
||||||
return loader(data, options, allocator)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
data, ok := os.read_entire_file(filename, allocator)
|
|
||||||
defer delete(data, allocator)
|
|
||||||
if ok {
|
|
||||||
return load_from_bytes(data, options, allocator)
|
|
||||||
} else {
|
|
||||||
return nil, .Unable_To_Read_File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
|
||||||
if img == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
context.allocator = allocator
|
|
||||||
destroyer := _internal_destroyers[img.which]
|
|
||||||
if destroyer != nil {
|
|
||||||
destroyer(img)
|
|
||||||
} else {
|
|
||||||
assert(img.metadata == nil)
|
|
||||||
bytes.buffer_destroy(&img.pixels)
|
|
||||||
free(img)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//+build !js
|
||||||
|
package image
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
load :: proc{
|
||||||
|
load_from_bytes,
|
||||||
|
load_from_file,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
data, ok := os.read_entire_file(filename, allocator)
|
||||||
|
defer delete(data, allocator)
|
||||||
|
if ok {
|
||||||
|
return load_from_bytes(data, options, allocator)
|
||||||
|
} else {
|
||||||
|
return nil, .Unable_To_Read_File
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
which :: proc{
|
||||||
|
which_bytes,
|
||||||
|
which_file,
|
||||||
|
}
|
||||||
|
|
||||||
|
which_file :: proc(path: string) -> Which_File_Type {
|
||||||
|
f, err := os.open(path)
|
||||||
|
if err != 0 {
|
||||||
|
return .Unknown
|
||||||
|
}
|
||||||
|
header: [128]byte
|
||||||
|
os.read(f, header[:])
|
||||||
|
file_type := which_bytes(header[:])
|
||||||
|
os.close(f)
|
||||||
|
return file_type
|
||||||
|
}
|
||||||
@@ -4,10 +4,10 @@ import "core:bytes"
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:os"
|
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:unicode"
|
import "core:unicode"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
Image :: image.Image
|
Image :: image.Image
|
||||||
Format :: image.Netpbm_Format
|
Format :: image.Netpbm_Format
|
||||||
@@ -26,23 +26,6 @@ PFM :: Formats{.Pf, .PF}
|
|||||||
ASCII :: Formats{.P1, .P2, .P3}
|
ASCII :: Formats{.P1, .P2, .P3}
|
||||||
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
|
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
|
||||||
|
|
||||||
load :: proc {
|
|
||||||
load_from_file,
|
|
||||||
load_from_bytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
|
||||||
if !ok {
|
|
||||||
err = .Unable_To_Read_File
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return load_from_bytes(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -66,24 +49,6 @@ load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^
|
|||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
save :: proc {
|
|
||||||
save_to_file,
|
|
||||||
save_to_buffer,
|
|
||||||
}
|
|
||||||
|
|
||||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
data: []byte; defer delete(data)
|
|
||||||
data = save_to_buffer(img, custom_info) or_return
|
|
||||||
|
|
||||||
if ok := os.write_entire_file(filename, data); !ok {
|
|
||||||
return .Unable_To_Write_File
|
|
||||||
}
|
|
||||||
|
|
||||||
return Format_Error.None
|
|
||||||
}
|
|
||||||
|
|
||||||
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -407,6 +372,8 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
|
|||||||
}
|
}
|
||||||
length = header_end_index + len(HEADER_END)
|
length = header_end_index + len(HEADER_END)
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
|
|
||||||
// string buffer for the tupltype
|
// string buffer for the tupltype
|
||||||
tupltype: strings.Builder
|
tupltype: strings.Builder
|
||||||
strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
|
strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
//+build js
|
||||||
|
package netpbm
|
||||||
|
|
||||||
|
load :: proc {
|
||||||
|
load_from_bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
save :: proc {
|
||||||
|
save_to_buffer,
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//+build !js
|
||||||
|
package netpbm
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
load :: proc {
|
||||||
|
load_from_file,
|
||||||
|
load_from_bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||||
|
if !ok {
|
||||||
|
err = .Unable_To_Read_File
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return load_from_bytes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
save :: proc {
|
||||||
|
save_to_file,
|
||||||
|
save_to_buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data: []byte; defer delete(data)
|
||||||
|
data = save_to_buffer(img, custom_info) or_return
|
||||||
|
|
||||||
|
if ok := os.write_entire_file(filename, data); !ok {
|
||||||
|
return .Unable_To_Write_File
|
||||||
|
}
|
||||||
|
|
||||||
|
return Format_Error.None
|
||||||
|
}
|
||||||
+17
-14
@@ -16,6 +16,7 @@ import coretime "core:time"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cleanup of image-specific data.
|
Cleanup of image-specific data.
|
||||||
@@ -91,6 +92,8 @@ core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
|
text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
assert(len(c.data) == int(c.header.length))
|
assert(len(c.data) == int(c.header.length))
|
||||||
#partial switch c.header.type {
|
#partial switch c.header.type {
|
||||||
case .tEXt:
|
case .tEXt:
|
||||||
@@ -194,18 +197,18 @@ text_destroy :: proc(text: Text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
||||||
ok = true
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
|
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
|
||||||
|
|
||||||
if len(fields[0]) < 1 || len(fields[0]) > 79 {
|
if len(fields[0]) < 1 || len(fields[0]) > 79 {
|
||||||
// Invalid profile name
|
// Invalid profile name
|
||||||
ok = false; return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(fields[1]) != 0 {
|
if len(fields[1]) != 0 {
|
||||||
// Compression method should be a zero, which the split turned into an empty slice.
|
// Compression method should be a zero, which the split turned into an empty slice.
|
||||||
ok = false; return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up ZLIB context and decompress iCCP payload
|
// Set up ZLIB context and decompress iCCP payload
|
||||||
@@ -213,12 +216,12 @@ iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
|||||||
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf)
|
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf)
|
||||||
if zlib_error != nil {
|
if zlib_error != nil {
|
||||||
bytes.buffer_destroy(&buf)
|
bytes.buffer_destroy(&buf)
|
||||||
ok = false; return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res.name = strings.clone(string(fields[0]))
|
res.name = strings.clone(string(fields[0]))
|
||||||
res.profile = bytes.buffer_to_bytes(&buf)
|
res.profile = bytes.buffer_to_bytes(&buf)
|
||||||
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,18 +259,18 @@ plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
|
|||||||
|
|
||||||
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||||
if c.header.type != .sPLT {
|
if c.header.type != .sPLT {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
ok = true
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator)
|
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator)
|
||||||
if len(fields) != 2 {
|
if len(fields) != 2 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res.depth = fields[1][0]
|
res.depth = fields[1][0]
|
||||||
if res.depth != 8 && res.depth != 16 {
|
if res.depth != 8 && res.depth != 16 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := fields[1][1:]
|
data := fields[1][1:]
|
||||||
@@ -275,21 +278,21 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
|||||||
|
|
||||||
if res.depth == 8 {
|
if res.depth == 8 {
|
||||||
if len(data) % 6 != 0 {
|
if len(data) % 6 != 0 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
count = len(data) / 6
|
count = len(data) / 6
|
||||||
if count > 256 {
|
if count > 256 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res.entries = mem.slice_data_cast([][4]u8, data)
|
res.entries = mem.slice_data_cast([][4]u8, data)
|
||||||
} else { // res.depth == 16
|
} else { // res.depth == 16
|
||||||
if len(data) % 10 != 0 {
|
if len(data) % 10 != 0 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
count = len(data) / 10
|
count = len(data) / 10
|
||||||
if count > 256 {
|
if count > 256 {
|
||||||
return {}, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res.entries = mem.slice_data_cast([][4]u16, data)
|
res.entries = mem.slice_data_cast([][4]u16, data)
|
||||||
@@ -297,7 +300,7 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
|||||||
|
|
||||||
res.name = strings.clone(string(fields[0]))
|
res.name = strings.clone(string(fields[0]))
|
||||||
res.used = u16(count)
|
res.used = u16(count)
|
||||||
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-23
@@ -17,12 +17,12 @@ import "core:compress"
|
|||||||
import "core:compress/zlib"
|
import "core:compress/zlib"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
|
|
||||||
import "core:os"
|
|
||||||
import "core:hash"
|
import "core:hash"
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:io"
|
import "core:io"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:intrinsics"
|
import "core:intrinsics"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
// Limit chunk sizes.
|
// Limit chunk sizes.
|
||||||
// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
||||||
@@ -335,19 +335,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
|||||||
return img, err
|
return img, err
|
||||||
}
|
}
|
||||||
|
|
||||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename)
|
|
||||||
defer delete(data)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return load_from_bytes(data, options)
|
|
||||||
} else {
|
|
||||||
return nil, .Unable_To_Read_File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
options := options
|
options := options
|
||||||
@@ -1247,6 +1234,8 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
|||||||
|
|
||||||
// TODO: See about doing a Duff's #unroll where practicable
|
// TODO: See about doing a Duff's #unroll where practicable
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
// Apron so we don't need to special case first rows.
|
// Apron so we don't need to special case first rows.
|
||||||
up := make([]u8, row_stride, context.temp_allocator)
|
up := make([]u8, row_stride, context.temp_allocator)
|
||||||
ok = true
|
ok = true
|
||||||
@@ -1299,10 +1288,9 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @(optimization_mode="speed")
|
// @(optimization_mode="speed")
|
||||||
defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_check {
|
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
|
||||||
|
|
||||||
using params
|
using params
|
||||||
ok = true
|
|
||||||
|
|
||||||
row_stride_in := ((channels * width * depth) + 7) >> 3
|
row_stride_in := ((channels * width * depth) + 7) >> 3
|
||||||
row_stride_out := channels * width
|
row_stride_out := channels * width
|
||||||
@@ -1314,6 +1302,8 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
|||||||
|
|
||||||
// TODO: See about doing a Duff's #unroll where practicable
|
// TODO: See about doing a Duff's #unroll where practicable
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
// Apron so we don't need to special case first rows.
|
// Apron so we don't need to special case first rows.
|
||||||
up := make([]u8, row_stride_out, context.temp_allocator)
|
up := make([]u8, row_stride_out, context.temp_allocator)
|
||||||
|
|
||||||
@@ -1457,18 +1447,18 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// @(optimization_mode="speed")
|
// @(optimization_mode="speed")
|
||||||
defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
defilter_16 :: proc(params: ^Filter_Params) -> bool {
|
||||||
|
|
||||||
using params
|
using params
|
||||||
ok = true
|
|
||||||
|
|
||||||
stride := channels * 2
|
stride := channels * 2
|
||||||
row_stride := width * stride
|
row_stride := width * stride
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
// TODO: See about doing a Duff's #unroll where practicable
|
// TODO: See about doing a Duff's #unroll where practicable
|
||||||
// Apron so we don't need to special case first rows.
|
// Apron so we don't need to special case first rows.
|
||||||
up := make([]u8, row_stride, context.temp_allocator)
|
up := make([]u8, row_stride, context.temp_allocator)
|
||||||
@@ -1518,7 +1508,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
|||||||
dest = dest[row_stride:]
|
dest = dest[row_stride:]
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
|
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
|
||||||
@@ -1637,8 +1627,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
|
||||||
|
|
||||||
|
|
||||||
@(init, private)
|
@(init, private)
|
||||||
_register :: proc() {
|
_register :: proc() {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//+build js
|
||||||
|
package png
|
||||||
|
|
||||||
|
load :: proc{load_from_bytes, load_from_context}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//+build !js
|
||||||
|
package png
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data, ok := os.read_entire_file(filename)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return load_from_bytes(data, options)
|
||||||
|
} else {
|
||||||
|
return nil, .Unable_To_Read_File
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-31
@@ -15,7 +15,6 @@ package qoi
|
|||||||
import "core:image"
|
import "core:image"
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:os"
|
|
||||||
|
|
||||||
Error :: image.Error
|
Error :: image.Error
|
||||||
Image :: image.Image
|
Image :: image.Image
|
||||||
@@ -24,7 +23,7 @@ Options :: image.Options
|
|||||||
RGB_Pixel :: image.RGB_Pixel
|
RGB_Pixel :: image.RGB_Pixel
|
||||||
RGBA_Pixel :: image.RGBA_Pixel
|
RGBA_Pixel :: image.RGBA_Pixel
|
||||||
|
|
||||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
if img == nil {
|
if img == nil {
|
||||||
@@ -166,20 +165,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
defer bytes.buffer_destroy(out)
|
|
||||||
|
|
||||||
save_to_memory(out, img, options) or_return
|
|
||||||
write_ok := os.write_entire_file(output, out.buf[:])
|
|
||||||
|
|
||||||
return nil if write_ok else .Unable_To_Write_File
|
|
||||||
}
|
|
||||||
|
|
||||||
save :: proc{save_to_memory, save_to_file}
|
|
||||||
|
|
||||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
ctx := &compress.Context_Memory_Input{
|
ctx := &compress.Context_Memory_Input{
|
||||||
input_data = data,
|
input_data = data,
|
||||||
@@ -189,19 +174,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
|||||||
return img, err
|
return img, err
|
||||||
}
|
}
|
||||||
|
|
||||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename)
|
|
||||||
defer delete(data)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return load_from_bytes(data, options)
|
|
||||||
} else {
|
|
||||||
return nil, .Unable_To_Read_File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(optimization_mode="speed")
|
@(optimization_mode="speed")
|
||||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
@@ -359,8 +331,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cleanup of image-specific data.
|
Cleanup of image-specific data.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//+build js
|
||||||
|
package qoi
|
||||||
|
|
||||||
|
save :: proc{save_to_buffer}
|
||||||
|
|
||||||
|
load :: proc{load_from_bytes, load_from_context}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//+build !js
|
||||||
|
package qoi
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
import "core:bytes"
|
||||||
|
|
||||||
|
save :: proc{save_to_buffer, save_to_file}
|
||||||
|
|
||||||
|
|
||||||
|
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
defer bytes.buffer_destroy(out)
|
||||||
|
|
||||||
|
save_to_buffer(out, img, options) or_return
|
||||||
|
write_ok := os.write_entire_file(output, out.buf[:])
|
||||||
|
|
||||||
|
return nil if write_ok else .Unable_To_Write_File
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||||
|
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data, ok := os.read_entire_file(filename)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return load_from_bytes(data, options)
|
||||||
|
} else {
|
||||||
|
return nil, .Unable_To_Read_File
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-30
@@ -14,7 +14,6 @@ package tga
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:os"
|
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ GA_Pixel :: image.GA_Pixel
|
|||||||
RGB_Pixel :: image.RGB_Pixel
|
RGB_Pixel :: image.RGB_Pixel
|
||||||
RGBA_Pixel :: image.RGBA_Pixel
|
RGBA_Pixel :: image.RGBA_Pixel
|
||||||
|
|
||||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
if img == nil {
|
if img == nil {
|
||||||
@@ -92,20 +91,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
defer bytes.buffer_destroy(out)
|
|
||||||
|
|
||||||
save_to_memory(out, img, options) or_return
|
|
||||||
write_ok := os.write_entire_file(output, out.buf[:])
|
|
||||||
|
|
||||||
return nil if write_ok else .Unable_To_Write_File
|
|
||||||
}
|
|
||||||
|
|
||||||
save :: proc{save_to_memory, save_to_file}
|
|
||||||
|
|
||||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
options := options
|
options := options
|
||||||
@@ -398,20 +383,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
|||||||
return img, err
|
return img, err
|
||||||
}
|
}
|
||||||
|
|
||||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
|
||||||
context.allocator = allocator
|
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename)
|
|
||||||
defer delete(data)
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return load_from_bytes(data, options)
|
|
||||||
} else {
|
|
||||||
return nil, .Unable_To_Read_File
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
|
||||||
|
|
||||||
destroy :: proc(img: ^Image) {
|
destroy :: proc(img: ^Image) {
|
||||||
if img == nil || img.width == 0 || img.height == 0 {
|
if img == nil || img.width == 0 || img.height == 0 {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
//+build js
|
||||||
|
package tga
|
||||||
|
|
||||||
|
save :: proc{save_to_buffer}
|
||||||
|
load :: proc{load_from_bytes, load_from_context}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//+build !js
|
||||||
|
package tga
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
import "core:bytes"
|
||||||
|
|
||||||
|
save :: proc{save_to_buffer, save_to_file}
|
||||||
|
|
||||||
|
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
defer bytes.buffer_destroy(out)
|
||||||
|
|
||||||
|
save_to_buffer(out, img, options) or_return
|
||||||
|
write_ok := os.write_entire_file(output, out.buf[:])
|
||||||
|
|
||||||
|
return nil if write_ok else .Unable_To_Write_File
|
||||||
|
}
|
||||||
|
|
||||||
|
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data, ok := os.read_entire_file(filename)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return load_from_bytes(data, options)
|
||||||
|
} else {
|
||||||
|
return nil, .Unable_To_Read_File
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -283,7 +283,7 @@ wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -
|
|||||||
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
|
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
|
||||||
|
|
||||||
// x86 Targets (i386, amd64)
|
// x86 Targets (i386, amd64)
|
||||||
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
|
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
|
||||||
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -429,11 +429,11 @@ reflect :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T))
|
|||||||
b := N * (2 * dot(N, I))
|
b := N * (2 * dot(N, I))
|
||||||
return I - b
|
return I - b
|
||||||
}
|
}
|
||||||
refract :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T)) {
|
refract :: proc(I, Normal: $V/[$N]$E, eta: E) -> (out: V) where IS_ARRAY(V), IS_FLOAT(ELEM_TYPE(V)) {
|
||||||
dv := dot(N, I)
|
dv := dot(Normal, I)
|
||||||
k := 1 - eta*eta - (1 - dv*dv)
|
k := 1 - eta*eta * (1 - dv*dv)
|
||||||
a := I * eta
|
a := I * eta
|
||||||
b := N * eta*dv*math.sqrt(k)
|
b := Normal * (eta*dv+math.sqrt(k))
|
||||||
return (a - b) * E(int(k >= 0))
|
return (a - b) * E(int(k >= 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+61
-14
@@ -9,6 +9,13 @@ Arena_Kind :: enum uint {
|
|||||||
Buffer = 2, // Uses a fixed sized buffer.
|
Buffer = 2, // Uses a fixed sized buffer.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Arena is a generalized arena allocator that supports 3 different variants.
|
||||||
|
|
||||||
|
Growing: A linked list of `Memory_Block`s allocated with virtual memory.
|
||||||
|
Static: A single `Memory_Block` allocated with virtual memory.
|
||||||
|
Buffer: A single `Memory_Block` created from a user provided []byte.
|
||||||
|
*/
|
||||||
Arena :: struct {
|
Arena :: struct {
|
||||||
kind: Arena_Kind,
|
kind: Arena_Kind,
|
||||||
curr_block: ^Memory_Block,
|
curr_block: ^Memory_Block,
|
||||||
@@ -29,6 +36,8 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Initialization of an `Arena` to be a `.Growing` variant.
|
||||||
|
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
||||||
arena.kind = .Growing
|
arena.kind = .Growing
|
||||||
@@ -39,6 +48,8 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Initialization of an `Arena` to be a `.Static` variant.
|
||||||
|
// A static arena contains a single `Memory_Block` allocated with virtual memory.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
||||||
arena.kind = .Static
|
arena.kind = .Static
|
||||||
@@ -48,6 +59,8 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEF
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialization of an `Arena` to be a `.Buffer` variant.
|
||||||
|
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
||||||
if len(buffer) < size_of(Memory_Block) {
|
if len(buffer) < size_of(Memory_Block) {
|
||||||
@@ -71,6 +84,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocates memory from the provided arena.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
||||||
@@ -119,6 +133,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets the memory of a Static or Buffer arena to a specific `pos`ition (offset) and zeroes the previously used memory.
|
||||||
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
||||||
sync.mutex_guard(&arena.mutex)
|
sync.mutex_guard(&arena.mutex)
|
||||||
|
|
||||||
@@ -140,50 +155,72 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frees the last memory block of a Growing Arena
|
||||||
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
sync.mutex_guard(&arena.mutex)
|
|
||||||
if free_block := arena.curr_block; free_block != nil {
|
if free_block := arena.curr_block; free_block != nil {
|
||||||
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
||||||
|
arena.total_used -= free_block.used
|
||||||
|
arena.total_reserved -= free_block.reserved
|
||||||
|
|
||||||
arena.curr_block = free_block.prev
|
arena.curr_block = free_block.prev
|
||||||
memory_block_dealloc(free_block)
|
memory_block_dealloc(free_block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arena_free_all :: proc(arena: ^Arena) {
|
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
|
||||||
|
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
switch arena.kind {
|
switch arena.kind {
|
||||||
case .Growing:
|
case .Growing:
|
||||||
sync.mutex_guard(&arena.mutex)
|
sync.mutex_guard(&arena.mutex)
|
||||||
for arena.curr_block != nil {
|
// NOTE(bill): Free all but the first memory block (if it exists)
|
||||||
arena_growing_free_last_memory_block(arena)
|
for arena.curr_block != nil && arena.curr_block.prev != nil {
|
||||||
|
arena_growing_free_last_memory_block(arena, loc)
|
||||||
}
|
}
|
||||||
arena.total_reserved = 0
|
// Zero the first block's memory
|
||||||
|
if arena.curr_block != nil {
|
||||||
|
mem.zero(arena.curr_block.base, int(arena.curr_block.used))
|
||||||
|
arena.curr_block.used = 0
|
||||||
|
}
|
||||||
|
arena.total_used = 0
|
||||||
case .Static, .Buffer:
|
case .Static, .Buffer:
|
||||||
arena_static_reset_to(arena, 0)
|
arena_static_reset_to(arena, 0)
|
||||||
}
|
}
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
arena_destroy :: proc(arena: ^Arena) {
|
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
|
||||||
arena_free_all(arena)
|
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
|
||||||
if arena.kind != .Buffer {
|
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
|
sync.mutex_guard(&arena.mutex)
|
||||||
|
switch arena.kind {
|
||||||
|
case .Growing:
|
||||||
|
for arena.curr_block != nil {
|
||||||
|
arena_growing_free_last_memory_block(arena, loc)
|
||||||
|
}
|
||||||
|
case .Static:
|
||||||
memory_block_dealloc(arena.curr_block)
|
memory_block_dealloc(arena.curr_block)
|
||||||
|
case .Buffer:
|
||||||
|
// nothing
|
||||||
}
|
}
|
||||||
arena.curr_block = nil
|
arena.curr_block = nil
|
||||||
arena.total_used = 0
|
arena.total_used = 0
|
||||||
arena.total_reserved = 0
|
arena.total_reserved = 0
|
||||||
arena.temp_count = 0
|
arena.temp_count = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
arena_growing_bootstrap_new :: proc{
|
arena_growing_bootstrap_new :: proc{
|
||||||
arena_growing_bootstrap_new_by_offset,
|
arena_growing_bootstrap_new_by_offset,
|
||||||
arena_growing_bootstrap_new_by_name,
|
arena_growing_bootstrap_new_by_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
||||||
arena_static_bootstrap_new :: proc{
|
arena_static_bootstrap_new :: proc{
|
||||||
arena_static_bootstrap_new_by_offset,
|
arena_static_bootstrap_new_by_offset,
|
||||||
arena_static_bootstrap_new_by_name,
|
arena_static_bootstrap_new_by_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
bootstrap: Arena
|
bootstrap: Arena
|
||||||
@@ -199,11 +236,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
bootstrap: Arena
|
bootstrap: Arena
|
||||||
@@ -219,17 +258,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||||
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create an `Allocator` from the provided `Arena`
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
||||||
return mem.Allocator{arena_allocator_proc, arena}
|
return mem.Allocator{arena_allocator_proc, arena}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The allocator procedured by an `Allocator` produced by `arena_allocator`
|
||||||
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||||
size, alignment: int,
|
size, alignment: int,
|
||||||
old_memory: rawptr, old_size: int,
|
old_memory: rawptr, old_size: int,
|
||||||
@@ -241,17 +283,17 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case .Alloc, .Alloc_Non_Zeroed:
|
case .Alloc, .Alloc_Non_Zeroed:
|
||||||
return arena_alloc(arena, size, alignment)
|
return arena_alloc(arena, size, alignment, location)
|
||||||
case .Free:
|
case .Free:
|
||||||
err = .Mode_Not_Implemented
|
err = .Mode_Not_Implemented
|
||||||
case .Free_All:
|
case .Free_All:
|
||||||
arena_free_all(arena)
|
arena_free_all(arena, location)
|
||||||
case .Resize:
|
case .Resize:
|
||||||
old_data := ([^]byte)(old_memory)
|
old_data := ([^]byte)(old_memory)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case old_data == nil:
|
case old_data == nil:
|
||||||
return arena_alloc(arena, size, alignment)
|
return arena_alloc(arena, size, alignment, location)
|
||||||
case size == old_size:
|
case size == old_size:
|
||||||
// return old memory
|
// return old memory
|
||||||
data = old_data[:size]
|
data = old_data[:size]
|
||||||
@@ -265,7 +307,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
new_memory := arena_alloc(arena, size, alignment) or_return
|
new_memory := arena_alloc(arena, size, alignment, location) or_return
|
||||||
if new_memory == nil {
|
if new_memory == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -286,12 +328,15 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state.
|
||||||
|
// All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`.
|
||||||
Arena_Temp :: struct {
|
Arena_Temp :: struct {
|
||||||
arena: ^Arena,
|
arena: ^Arena,
|
||||||
block: ^Memory_Block,
|
block: ^Memory_Block,
|
||||||
used: uint,
|
used: uint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Begins the section of temporary arena memory.
|
||||||
@(require_results)
|
@(require_results)
|
||||||
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
||||||
assert(arena != nil, "nil arena", loc)
|
assert(arena != nil, "nil arena", loc)
|
||||||
@@ -306,6 +351,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ends the section of temporary arena memory by resetting the memory to the stored position.
|
||||||
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
assert(temp.arena != nil, "nil arena", loc)
|
assert(temp.arena != nil, "nil arena", loc)
|
||||||
arena := temp.arena
|
arena := temp.arena
|
||||||
@@ -339,7 +385,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|||||||
arena.temp_count -= 1
|
arena.temp_count -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore the use of a `arena_temp_begin` entirely
|
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
|
||||||
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
assert(temp.arena != nil, "nil arena", loc)
|
assert(temp.arena != nil, "nil arena", loc)
|
||||||
arena := temp.arena
|
arena := temp.arena
|
||||||
@@ -349,6 +395,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
|||||||
arena.temp_count -= 1
|
arena.temp_count -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
|
||||||
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,744 @@
|
|||||||
|
// +build windows, linux, darwin
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expects an IPv4 address with no leading or trailing whitespace:
|
||||||
|
- a.b.c.d
|
||||||
|
- a.b.c.d:port
|
||||||
|
- [a.b.c.d]:port
|
||||||
|
|
||||||
|
If the IP address is bracketed, the port must be present and valid (though it will be ignored):
|
||||||
|
- [a.b.c.d] will be treated as a parsing failure.
|
||||||
|
|
||||||
|
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||||
|
|
||||||
|
If `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255.
|
||||||
|
*/
|
||||||
|
parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) {
|
||||||
|
res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return
|
||||||
|
return res.?
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parses an IP address in "non-decimal" `inet_aton` form.
|
||||||
|
|
||||||
|
e.g."00377.0x0ff.65534" = 255.255.255.254
|
||||||
|
00377 = 255 in octal
|
||||||
|
0x0ff = 255 in hexadecimal
|
||||||
|
This leaves 16 bits worth of address
|
||||||
|
.65534 then accounts for the last two digits
|
||||||
|
|
||||||
|
For the address part the allowed forms are:
|
||||||
|
a.b.c.d - where each part represents a byte
|
||||||
|
a.b.c - where `a` & `b` represent a byte and `c` a u16
|
||||||
|
a.b - where `a` represents a byte and `b` supplies the trailing 24 bits
|
||||||
|
a - where `a` gives the entire 32-bit value
|
||||||
|
|
||||||
|
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||||
|
*/
|
||||||
|
aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only := false) -> (addr: Address, ok: bool) {
|
||||||
|
switch family {
|
||||||
|
case .IP4:
|
||||||
|
// There is no valid address shorter than `0.0.0.0`.
|
||||||
|
if len(address_and_maybe_port) < 7 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
address, _ := split_port(address_and_maybe_port) or_return // This call doesn't allocate
|
||||||
|
|
||||||
|
buf: [4]u64 = {}
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
max_value := u64(max(u32))
|
||||||
|
bases := DEFAULT_DIGIT_BASES
|
||||||
|
|
||||||
|
if allow_decimal_only {
|
||||||
|
max_value = 255
|
||||||
|
bases = {.Dec}
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(address) > 0 {
|
||||||
|
if i == 4 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimal-only addresses may not have a leading zero.
|
||||||
|
if allow_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
number, consumed, number_ok := parse_ip_component(address, max_value, bases)
|
||||||
|
if !number_ok || consumed == 0 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[i] = number
|
||||||
|
|
||||||
|
address = address[consumed:]
|
||||||
|
|
||||||
|
if len(address) > 0 && address[0] == '.' {
|
||||||
|
address = address[1:]
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute parts.
|
||||||
|
switch i {
|
||||||
|
case 1:
|
||||||
|
buf[1] = buf[0] & 0xffffff
|
||||||
|
buf[0] >>= 24
|
||||||
|
fallthrough
|
||||||
|
case 2:
|
||||||
|
buf[2] = buf[1] & 0xffff
|
||||||
|
buf[1] >>= 16
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
buf[3] = buf[2] & 0xff
|
||||||
|
buf[2] >>= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
a: [4]u8 = ---
|
||||||
|
for v, i in buf {
|
||||||
|
if v > 255 { return {}, false }
|
||||||
|
a[i] = u8(v)
|
||||||
|
}
|
||||||
|
return IP4_Address(a), true
|
||||||
|
|
||||||
|
case .IP6:
|
||||||
|
return parse_ip6_address(address_and_maybe_port)
|
||||||
|
|
||||||
|
case:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The minimum length of a valid IPv6 address string is 2, e.g. `::`
|
||||||
|
|
||||||
|
The maximum length of a valid IPv6 address string is 45, when it embeds an IPv4,
|
||||||
|
e.g. `0000:0000:0000:0000:0000:ffff:255.255.255.255`
|
||||||
|
|
||||||
|
An IPv6 address must contain at least 3 pieces, e.g. `::`,
|
||||||
|
and at most 9 (using `::` for a trailing or leading 0)
|
||||||
|
*/
|
||||||
|
IPv6_MIN_STRING_LENGTH :: 2
|
||||||
|
IPv6_MAX_STRING_LENGTH :: 45
|
||||||
|
IPv6_MIN_COLONS :: 2
|
||||||
|
IPv6_PIECE_COUNT :: 8
|
||||||
|
|
||||||
|
parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) {
|
||||||
|
// If we have an IPv6 address of the form [IP]:Port, first get us just the IP.
|
||||||
|
address, _ := split_port(address_and_maybe_port) or_return
|
||||||
|
|
||||||
|
// Early bailouts based on length and number of pieces.
|
||||||
|
if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Do a pre-pass on the string that checks how many `:` and `.` we have,
|
||||||
|
if they're in the right order, and if the things between them are digits as expected.
|
||||||
|
|
||||||
|
It's not strictly necessary considering we could use `strings.split`,
|
||||||
|
but this way we can avoid using an allocator and return earlier on bogus input. Win-win.
|
||||||
|
*/
|
||||||
|
colon_count := 0
|
||||||
|
dot_count := 0
|
||||||
|
|
||||||
|
pieces_temp: [IPv6_PIECE_COUNT + 1]string
|
||||||
|
|
||||||
|
piece_start := 0
|
||||||
|
piece_end := 0
|
||||||
|
|
||||||
|
for ch, i in address {
|
||||||
|
switch ch {
|
||||||
|
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||||
|
piece_end += 1
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
// If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, instead of it being the tail: invalid.
|
||||||
|
if dot_count > 0 { return }
|
||||||
|
|
||||||
|
pieces_temp[colon_count] = address[piece_start:piece_end]
|
||||||
|
|
||||||
|
colon_count += 1
|
||||||
|
if colon_count > IPv6_PIECE_COUNT { return }
|
||||||
|
|
||||||
|
// If there's anything left, put it in the next piece.
|
||||||
|
piece_start = i + 1
|
||||||
|
piece_end = piece_start
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
// IPv4 address is treated as one piece. No need to update `piece_*`.
|
||||||
|
dot_count += 1
|
||||||
|
|
||||||
|
case: // Invalid character, return early
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if colon_count < IPv6_MIN_COLONS { return }
|
||||||
|
|
||||||
|
// Assign the last piece string.
|
||||||
|
pieces_temp[colon_count] = address[piece_start:]
|
||||||
|
|
||||||
|
// `pieces` now holds the same output as it would if had used `strings.split`.
|
||||||
|
pieces := pieces_temp[:colon_count + 1]
|
||||||
|
|
||||||
|
// Check if we have what looks like an embedded IPv4 address.
|
||||||
|
ipv4: IP4_Address
|
||||||
|
have_ipv4: bool
|
||||||
|
|
||||||
|
if dot_count > 0 {
|
||||||
|
/*
|
||||||
|
If we have an IPv4 address accounting for the last 32 bits,
|
||||||
|
this means we can have at most 6 IPv6 pieces, like so: `x:x:X:x:x:x:d.d.d.d`
|
||||||
|
|
||||||
|
Or, put differently: 6 pieces IPv6 (5 colons), a colon, 1 piece IPv4 (3 dots),
|
||||||
|
for a total of 6 colons and 3 dots.
|
||||||
|
*/
|
||||||
|
if dot_count != 3 || colon_count > 6 { return }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Try to parse IPv4 address.
|
||||||
|
If successful, we have our least significant 32 bits.
|
||||||
|
If not, it invalidates the whole address and we can bail.
|
||||||
|
*/
|
||||||
|
ipv4, have_ipv4 = parse_ip4_address(pieces_temp[colon_count])
|
||||||
|
if !have_ipv4 { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for `::` being used more than once, and save the skip.
|
||||||
|
zero_skip := -1
|
||||||
|
for i in 1..<colon_count {
|
||||||
|
if pieces[i] == "" {
|
||||||
|
// Return if skip has already been set.
|
||||||
|
if zero_skip != -1 { return }
|
||||||
|
zero_skip = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Now check if we have the necessary number pieces, accounting for any `::`,
|
||||||
|
and how many were skipped by it if applicable.
|
||||||
|
*/
|
||||||
|
before_skip := 0
|
||||||
|
after_skip := 0
|
||||||
|
num_skipped := 0
|
||||||
|
|
||||||
|
if zero_skip != -1 {
|
||||||
|
before_skip = zero_skip
|
||||||
|
after_skip = colon_count - zero_skip
|
||||||
|
|
||||||
|
// An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||||
|
if have_ipv4 {
|
||||||
|
after_skip += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust for leading `::`.
|
||||||
|
if pieces[0] == "" {
|
||||||
|
before_skip -= 1
|
||||||
|
// Leading `:` can only be part of `::`.
|
||||||
|
if before_skip > 0 { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust for trailing `::`.
|
||||||
|
if pieces[colon_count] == "" {
|
||||||
|
after_skip -= 1
|
||||||
|
// Trailing `:` can only be part of `::`.
|
||||||
|
if after_skip > 0 { return }
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate how many zero pieces we skipped.
|
||||||
|
It should be at least one, considering we encountered a `::`.
|
||||||
|
*/
|
||||||
|
num_skipped = IPv6_PIECE_COUNT - before_skip - after_skip
|
||||||
|
if num_skipped < 1 { return }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
No zero skip means everything is part of "before the skip".
|
||||||
|
An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||||
|
*/
|
||||||
|
piece_count := colon_count + 1
|
||||||
|
if have_ipv4 {
|
||||||
|
piece_count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have the complete set?
|
||||||
|
if piece_count != IPv6_PIECE_COUNT { return }
|
||||||
|
|
||||||
|
// Validate leading and trailing empty parts, as they can only be part of a `::`.
|
||||||
|
if pieces[0] == "" || pieces[colon_count] == "" { return }
|
||||||
|
|
||||||
|
|
||||||
|
before_skip = piece_count
|
||||||
|
after_skip = 0
|
||||||
|
num_skipped = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to parse the pieces into a 8 16-bit pieces.
|
||||||
|
piece_values: [IPv6_PIECE_COUNT]u16be
|
||||||
|
|
||||||
|
idx := 0
|
||||||
|
val_idx := 0
|
||||||
|
|
||||||
|
for _ in 0..<before_skip {
|
||||||
|
/*
|
||||||
|
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||||
|
If we have an IPv4 address, stop on the penultimate index.
|
||||||
|
*/
|
||||||
|
if have_ipv4 && val_idx == 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
piece := pieces[idx]
|
||||||
|
|
||||||
|
// An IPv6 piece can at most contain 4 hex digits.
|
||||||
|
if len(piece) > 4 { return }
|
||||||
|
|
||||||
|
if piece != "" {
|
||||||
|
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||||
|
piece_values[val_idx] = u16be(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1
|
||||||
|
val_idx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if before_skip == 0 {
|
||||||
|
idx += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if num_skipped > 0 {
|
||||||
|
idx += 1
|
||||||
|
val_idx += num_skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
if after_skip > 0 {
|
||||||
|
for _ in 0..<after_skip {
|
||||||
|
/*
|
||||||
|
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||||
|
If we have an IPv4 address, stop on the penultimate index.
|
||||||
|
*/
|
||||||
|
if have_ipv4 && val_idx == 6 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
piece := pieces[idx]
|
||||||
|
|
||||||
|
// An IPv6 piece can contain at most 4 hex digits.
|
||||||
|
if len(piece) > 4 { return }
|
||||||
|
|
||||||
|
if piece != "" {
|
||||||
|
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||||
|
piece_values[val_idx] = u16be(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1
|
||||||
|
val_idx += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute IPv4 address into last two pieces, if applicable.
|
||||||
|
if have_ipv4 {
|
||||||
|
val := u16(ipv4[0]) << 8
|
||||||
|
val |= u16(ipv4[1])
|
||||||
|
piece_values[6] = u16be(val)
|
||||||
|
|
||||||
|
val = u16(ipv4[2]) << 8
|
||||||
|
val |= u16(ipv4[3])
|
||||||
|
piece_values[7] = u16be(val)
|
||||||
|
}
|
||||||
|
return transmute(IP6_Address)piece_values, true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Try parsing as an IPv6 address.
|
||||||
|
If it's determined not to be, try as an IPv4 address, optionally in non-decimal format.
|
||||||
|
*/
|
||||||
|
parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address {
|
||||||
|
if addr6, ok6 := parse_ip6_address(address_and_maybe_port); ok6 {
|
||||||
|
return addr6
|
||||||
|
}
|
||||||
|
if addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address); ok4 {
|
||||||
|
return addr4
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) {
|
||||||
|
if addr_str, port, split_ok := split_port(endpoint_str); split_ok {
|
||||||
|
if addr := parse_address(addr_str); addr != nil {
|
||||||
|
return Endpoint { address = addr, port = port }, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Host :: struct {
|
||||||
|
hostname: string,
|
||||||
|
port: int,
|
||||||
|
}
|
||||||
|
Host_Or_Endpoint :: union {
|
||||||
|
Host,
|
||||||
|
Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a string consisting of a hostname or IP address, and an optional port,
|
||||||
|
// and return the component parts in a useful form.
|
||||||
|
parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) {
|
||||||
|
host, port, port_ok := split_port(endpoint_str)
|
||||||
|
if !port_ok {
|
||||||
|
return nil, .Bad_Port
|
||||||
|
}
|
||||||
|
if addr := parse_address(host); addr != nil {
|
||||||
|
return Endpoint{addr, port}, .None
|
||||||
|
}
|
||||||
|
if !validate_hostname(host) {
|
||||||
|
return nil, .Bad_Hostname
|
||||||
|
}
|
||||||
|
return Host{host, port}, .None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Takes an endpoint string and returns its parts.
|
||||||
|
// Returns ok=false if port is not a number.
|
||||||
|
split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) {
|
||||||
|
// IP6 [addr_or_host]:port
|
||||||
|
if i := strings.last_index(endpoint_str, "]:"); i >= 0 {
|
||||||
|
addr_or_host = endpoint_str[1:i]
|
||||||
|
port, ok = strconv.parse_int(endpoint_str[i+2:], 10)
|
||||||
|
|
||||||
|
if port > 65535 {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := strings.count(endpoint_str, ":"); n == 1 {
|
||||||
|
// IP4 addr_or_host:port
|
||||||
|
i := strings.last_index(endpoint_str, ":")
|
||||||
|
assert(i != -1)
|
||||||
|
|
||||||
|
addr_or_host = endpoint_str[:i]
|
||||||
|
port, ok = strconv.parse_int(endpoint_str[i+1:], 10)
|
||||||
|
|
||||||
|
if port > 65535 {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if n > 1 {
|
||||||
|
// IP6 address without port
|
||||||
|
}
|
||||||
|
|
||||||
|
// No port
|
||||||
|
addr_or_host = endpoint_str
|
||||||
|
port = 0
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joins an address or hostname with a port.
|
||||||
|
join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string {
|
||||||
|
addr_or_host, _, ok := split_port(address_or_host)
|
||||||
|
if !ok do return addr_or_host
|
||||||
|
|
||||||
|
b := strings.builder_make(allocator)
|
||||||
|
|
||||||
|
addr := parse_address(addr_or_host)
|
||||||
|
if addr == nil {
|
||||||
|
// hostname
|
||||||
|
fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
|
||||||
|
} else {
|
||||||
|
switch in addr {
|
||||||
|
case IP4_Address:
|
||||||
|
fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
|
||||||
|
case IP6_Address:
|
||||||
|
fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.to_string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO(tetra): Do we need this?
|
||||||
|
map_to_ip6 :: proc(addr: Address) -> Address {
|
||||||
|
if addr6, ok := addr.(IP6_Address); ok {
|
||||||
|
return addr6
|
||||||
|
}
|
||||||
|
addr4 := addr.(IP4_Address)
|
||||||
|
addr4_u16 := transmute([2]u16be) addr4
|
||||||
|
addr6: IP6_Address
|
||||||
|
addr6[4] = 0xffff
|
||||||
|
copy(addr6[5:], addr4_u16[:])
|
||||||
|
return addr6
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a temporarily-allocated string representation of the address.
|
||||||
|
|
||||||
|
See RFC 5952 section 4 for IPv6 representation recommendations.
|
||||||
|
*/
|
||||||
|
address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string {
|
||||||
|
b := strings.builder_make(allocator)
|
||||||
|
switch v in addr {
|
||||||
|
case IP4_Address:
|
||||||
|
fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
|
||||||
|
case IP6_Address:
|
||||||
|
// First find the longest run of zeroes.
|
||||||
|
Zero_Run :: struct {
|
||||||
|
start: int,
|
||||||
|
end: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We're dealing with 0-based indices, appropriately enough for runs of zeroes.
|
||||||
|
Still, it means we need to initialize runs with some value outside of the possible range.
|
||||||
|
*/
|
||||||
|
run := Zero_Run{-1, -1}
|
||||||
|
best := Zero_Run{-1, -1}
|
||||||
|
|
||||||
|
addr := transmute([8]u16be)v
|
||||||
|
|
||||||
|
last := u16be(1)
|
||||||
|
for val, i in addr {
|
||||||
|
/*
|
||||||
|
If we encounter adjacent zeroes, then start a new run if not already in one.
|
||||||
|
Also remember the rightmost index regardless, because it'll be the new
|
||||||
|
frontier of both new and existing runs.
|
||||||
|
*/
|
||||||
|
if last == 0 && val == 0 {
|
||||||
|
run.end = i
|
||||||
|
if run.start == -1 {
|
||||||
|
run.start = i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If we're in a run check if its length is better than the best recorded so far.
|
||||||
|
If so, update the best run's start and end.
|
||||||
|
*/
|
||||||
|
if run.start != -1 {
|
||||||
|
length_to_beat := best.end - best.start
|
||||||
|
length := run.end - run.start
|
||||||
|
|
||||||
|
if length > length_to_beat {
|
||||||
|
best = run
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were in a run, this is where we reset it.
|
||||||
|
if val != 0 {
|
||||||
|
run = {-1, -1}
|
||||||
|
}
|
||||||
|
|
||||||
|
last = val
|
||||||
|
}
|
||||||
|
|
||||||
|
for val, i in addr {
|
||||||
|
if best.start == i || best.end == i {
|
||||||
|
// For the left and right side of the best zero run, print a `:`.
|
||||||
|
fmt.sbprint(&b, ":")
|
||||||
|
} else if i < best.start {
|
||||||
|
/*
|
||||||
|
If we haven't made it to the best run yet, print the digit.
|
||||||
|
Make sure we only print a `:` after the digit if it's not
|
||||||
|
immediately followed by the run's own leftmost `:`.
|
||||||
|
*/
|
||||||
|
fmt.sbprintf(&b, "%x", val)
|
||||||
|
if i < best.start - 1 {
|
||||||
|
fmt.sbprintf(&b, ":")
|
||||||
|
}
|
||||||
|
} else if i > best.end {
|
||||||
|
/*
|
||||||
|
If there are any digits after the zero run, print them.
|
||||||
|
But don't print the `:` at the end of the IP number.
|
||||||
|
*/
|
||||||
|
fmt.sbprintf(&b, "%x", val)
|
||||||
|
if i != 7 {
|
||||||
|
fmt.sbprintf(&b, ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.to_string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a temporarily-allocated string representation of the endpoint.
|
||||||
|
// If there's a port, uses the `[address]:port` format.
|
||||||
|
endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string {
|
||||||
|
if ep.port == 0 {
|
||||||
|
return address_to_string(ep.address, allocator)
|
||||||
|
} else {
|
||||||
|
s := address_to_string(ep.address, context.temp_allocator)
|
||||||
|
b := strings.builder_make(allocator)
|
||||||
|
switch a in ep.address {
|
||||||
|
case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port)
|
||||||
|
case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
|
||||||
|
}
|
||||||
|
return strings.to_string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string :: proc{address_to_string, endpoint_to_string}
|
||||||
|
|
||||||
|
|
||||||
|
family_from_address :: proc(addr: Address) -> Address_Family {
|
||||||
|
switch in addr {
|
||||||
|
case IP4_Address: return .IP4
|
||||||
|
case IP6_Address: return .IP6
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
family_from_endpoint :: proc(ep: Endpoint) -> Address_Family {
|
||||||
|
return family_from_address(ep.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Digit_Parse_Base :: enum u8 {
|
||||||
|
Dec = 0, // No prefix
|
||||||
|
Oct = 1, // Leading zero
|
||||||
|
Hex = 2, // 0x prefix
|
||||||
|
IPv6 = 3, // Unprefixed IPv6 piece hex. Can't be used with other bases.
|
||||||
|
}
|
||||||
|
Digit_Parse_Bases :: bit_set[Digit_Parse_Base; u8]
|
||||||
|
DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parses a single unsigned number in requested `bases` from `input`.
|
||||||
|
`max_value` represents the maximum allowed value for this number.
|
||||||
|
|
||||||
|
Returns the `value`, the `bytes_consumed` so far, and `ok` to signal success or failure.
|
||||||
|
|
||||||
|
An out-of-range or invalid number will return the accumulated value so far (which can be out of range),
|
||||||
|
the number of bytes consumed leading up the error, and `ok = false`.
|
||||||
|
|
||||||
|
When `.` or `:` are encountered, they'll be considered valid separators and will stop parsing,
|
||||||
|
returning the valid number leading up to it.
|
||||||
|
|
||||||
|
Other non-digit characters are treated as an error.
|
||||||
|
|
||||||
|
Octal numbers are expected to have a leading zero, with no 'o' format specifier.
|
||||||
|
Hexadecimal numbers are expected to be preceded by '0x' or '0X'.
|
||||||
|
Numbers will otherwise be considered to be in base 10.
|
||||||
|
*/
|
||||||
|
parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) {
|
||||||
|
// Default to base 10
|
||||||
|
base := u64(10)
|
||||||
|
input := input
|
||||||
|
|
||||||
|
/*
|
||||||
|
We keep track of the number of prefix bytes and digit bytes separately.
|
||||||
|
This way if a prefix is consumed and we encounter a separator or the end of the string,
|
||||||
|
the number is only considered valid if at least 1 digit byte has been consumed and the value is within range.
|
||||||
|
*/
|
||||||
|
prefix_bytes := 0
|
||||||
|
digit_bytes := 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
IPv6 hex bytes are unprefixed and can't be disambiguated from octal or hex unless the digit is out of range.
|
||||||
|
If we got the `.IPv6` option, skip prefix scanning and other flags aren't also used.
|
||||||
|
*/
|
||||||
|
if .IPv6 in bases {
|
||||||
|
if bases != {.IPv6} { return } // Must be used on its own.
|
||||||
|
base = 16
|
||||||
|
} else {
|
||||||
|
// Scan for and consume prefix, if applicable.
|
||||||
|
if len(input) >= 2 && input[0] == '0' {
|
||||||
|
if .Hex in bases && (input[1] == 'x' || input[1] == 'X') {
|
||||||
|
base = 16
|
||||||
|
input = input[2:]
|
||||||
|
prefix_bytes = 2
|
||||||
|
}
|
||||||
|
if prefix_bytes == 0 && .Oct in bases {
|
||||||
|
base = 8
|
||||||
|
input = input[1:]
|
||||||
|
prefix_bytes = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_loop: for ch in input {
|
||||||
|
switch ch {
|
||||||
|
case '0'..='7':
|
||||||
|
digit_bytes += 1
|
||||||
|
value = value * base + u64(ch - '0')
|
||||||
|
|
||||||
|
case '8'..='9':
|
||||||
|
digit_bytes += 1
|
||||||
|
|
||||||
|
if base == 8 {
|
||||||
|
// Out of range for octal numbers.
|
||||||
|
return value, digit_bytes + prefix_bytes, false
|
||||||
|
}
|
||||||
|
value = value * base + u64(ch - '0')
|
||||||
|
|
||||||
|
case 'a'..='f':
|
||||||
|
digit_bytes += 1
|
||||||
|
|
||||||
|
if base == 8 || base == 10 {
|
||||||
|
// Out of range for octal and decimal numbers.
|
||||||
|
return value, digit_bytes + prefix_bytes, false
|
||||||
|
}
|
||||||
|
value = value * base + (u64(ch - 'a') + 10)
|
||||||
|
|
||||||
|
case 'A'..='F':
|
||||||
|
digit_bytes += 1
|
||||||
|
|
||||||
|
if base == 8 || base == 10 {
|
||||||
|
// Out of range for octal and decimal numbers.
|
||||||
|
return value, digit_bytes + prefix_bytes, false
|
||||||
|
}
|
||||||
|
value = value * base + (u64(ch - 'A') + 10)
|
||||||
|
|
||||||
|
case '.', ':':
|
||||||
|
/*
|
||||||
|
Number separator. Return early.
|
||||||
|
We don't need to check if the number is in range.
|
||||||
|
We do that each time through the loop.
|
||||||
|
*/
|
||||||
|
break parse_loop
|
||||||
|
|
||||||
|
case:
|
||||||
|
// Invalid character encountered.
|
||||||
|
return value, digit_bytes + prefix_bytes, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if value > max_value {
|
||||||
|
// Out-of-range number.
|
||||||
|
return value, digit_bytes + prefix_bytes, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range.
|
||||||
|
return value, digit_bytes + prefix_bytes, digit_bytes >= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an address for each interface that can be bound to.
|
||||||
|
get_network_interfaces :: proc() -> []Address {
|
||||||
|
// TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,416 @@
|
|||||||
|
// +build windows, linux, darwin
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
|
||||||
|
This file collects structs, enums and settings applicable to the entire package in one handy place.
|
||||||
|
Platform-specific ones can be found in their respective `*_windows.odin` and similar files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
|
/*
|
||||||
|
TUNEABLES - See also top of `dns.odin` for DNS configuration.
|
||||||
|
|
||||||
|
Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new
|
||||||
|
socket, and the client socket, respectively.
|
||||||
|
This can also be set on a per-socket basis using the 'options' optional parameter to those procedures.
|
||||||
|
|
||||||
|
When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being
|
||||||
|
coalesced into fewer network packets.
|
||||||
|
|
||||||
|
This makes the networking layer more eagerly send data when you ask it to,
|
||||||
|
which can reduce latency by up to 200ms.
|
||||||
|
|
||||||
|
This does mean that a lot of small writes will negatively effect throughput however,
|
||||||
|
since the Nagle algorithm will be disabled, and each write becomes one
|
||||||
|
IP packet. This will increase traffic by a factor of 40, with IP and TCP
|
||||||
|
headers for each payload.
|
||||||
|
|
||||||
|
However, you can avoid this by buffering things up yourself if you wish to send a lot of
|
||||||
|
short data chunks, when TCP_NODELAY is enabled on that socket.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
|
||||||
|
|
||||||
|
// COMMON DEFINITIONS
|
||||||
|
Maybe :: runtime.Maybe
|
||||||
|
|
||||||
|
Network_Error :: union #shared_nil {
|
||||||
|
General_Error,
|
||||||
|
Platform_Error,
|
||||||
|
Create_Socket_Error,
|
||||||
|
Dial_Error,
|
||||||
|
Listen_Error,
|
||||||
|
Accept_Error,
|
||||||
|
Bind_Error,
|
||||||
|
TCP_Send_Error,
|
||||||
|
UDP_Send_Error,
|
||||||
|
TCP_Recv_Error,
|
||||||
|
UDP_Recv_Error,
|
||||||
|
Shutdown_Error,
|
||||||
|
Socket_Option_Error,
|
||||||
|
Set_Blocking_Error,
|
||||||
|
Parse_Endpoint_Error,
|
||||||
|
Resolve_Error,
|
||||||
|
DNS_Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
General_Error :: enum u32 {
|
||||||
|
None = 0,
|
||||||
|
Unable_To_Enumerate_Network_Interfaces = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
|
||||||
|
Platform_Error :: enum u32 {}
|
||||||
|
|
||||||
|
Parse_Endpoint_Error :: enum {
|
||||||
|
None = 0,
|
||||||
|
Bad_Port = 1,
|
||||||
|
Bad_Address,
|
||||||
|
Bad_Hostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
Resolve_Error :: enum u32 {
|
||||||
|
None = 0,
|
||||||
|
Unable_To_Resolve = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Error :: enum u32 {
|
||||||
|
Invalid_Hostname_Error = 1,
|
||||||
|
Invalid_Hosts_Config_Error,
|
||||||
|
Invalid_Resolv_Config_Error,
|
||||||
|
Connection_Error,
|
||||||
|
Server_Error,
|
||||||
|
System_Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOCKET OPTIONS & DEFINITIONS
|
||||||
|
TCP_Options :: struct {
|
||||||
|
no_delay: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
default_tcp_options := TCP_Options {
|
||||||
|
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To allow freely using `Socket` in your own data structures in a cross-platform manner,
|
||||||
|
we treat it as a handle large enough to accomodate OS-specific notions of socket handles.
|
||||||
|
|
||||||
|
The platform code will perform the cast so you don't have to.
|
||||||
|
*/
|
||||||
|
Socket :: distinct i64
|
||||||
|
|
||||||
|
TCP_Socket :: distinct Socket
|
||||||
|
UDP_Socket :: distinct Socket
|
||||||
|
|
||||||
|
Socket_Protocol :: enum {
|
||||||
|
TCP,
|
||||||
|
UDP,
|
||||||
|
}
|
||||||
|
|
||||||
|
Any_Socket :: union {
|
||||||
|
TCP_Socket,
|
||||||
|
UDP_Socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ADDRESS DEFINITIONS
|
||||||
|
*/
|
||||||
|
|
||||||
|
IP4_Address :: distinct [4]u8
|
||||||
|
IP6_Address :: distinct [8]u16be
|
||||||
|
Address :: union {IP4_Address, IP6_Address}
|
||||||
|
|
||||||
|
IP4_Loopback := IP4_Address{127, 0, 0, 1}
|
||||||
|
IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
|
||||||
|
|
||||||
|
IP4_Any := IP4_Address{}
|
||||||
|
IP6_Any := IP6_Address{}
|
||||||
|
|
||||||
|
Endpoint :: struct {
|
||||||
|
address: Address,
|
||||||
|
port: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Address_Family :: enum {
|
||||||
|
IP4,
|
||||||
|
IP6,
|
||||||
|
}
|
||||||
|
|
||||||
|
Netmask :: distinct Address
|
||||||
|
|
||||||
|
/*
|
||||||
|
INTERFACE / LINK STATE
|
||||||
|
*/
|
||||||
|
Network_Interface :: struct {
|
||||||
|
adapter_name: string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage.
|
||||||
|
friendly_name: string,
|
||||||
|
description: string,
|
||||||
|
dns_suffix: string,
|
||||||
|
|
||||||
|
physical_address: string, // MAC address, etc.
|
||||||
|
mtu: u32,
|
||||||
|
|
||||||
|
unicast: [dynamic]Lease,
|
||||||
|
multicast: [dynamic]Address,
|
||||||
|
anycast: [dynamic]Address,
|
||||||
|
|
||||||
|
gateways: [dynamic]Address,
|
||||||
|
dhcp_v4: Address,
|
||||||
|
dhcp_v6: Address,
|
||||||
|
|
||||||
|
tunnel_type: Tunnel_Type,
|
||||||
|
|
||||||
|
link: struct {
|
||||||
|
state: Link_State,
|
||||||
|
transmit_speed: u64,
|
||||||
|
receive_speed: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty bit set is unknown state.
|
||||||
|
Link_States :: enum u32 {
|
||||||
|
Up = 1,
|
||||||
|
Down = 2,
|
||||||
|
Testing = 3,
|
||||||
|
Dormant = 4,
|
||||||
|
Not_Present = 5,
|
||||||
|
Lower_Layer_Down = 6,
|
||||||
|
Loopback = 7,
|
||||||
|
}
|
||||||
|
Link_State :: bit_set[Link_States; u32]
|
||||||
|
|
||||||
|
Lease :: struct {
|
||||||
|
address: Address,
|
||||||
|
netmask: Netmask,
|
||||||
|
lifetime: struct {
|
||||||
|
valid: u32,
|
||||||
|
preferred: u32,
|
||||||
|
lease: u32,
|
||||||
|
},
|
||||||
|
origin: struct {
|
||||||
|
prefix: Prefix_Origin,
|
||||||
|
suffix: Suffix_Origin,
|
||||||
|
},
|
||||||
|
address_duplication: Address_Duplication,
|
||||||
|
}
|
||||||
|
|
||||||
|
Tunnel_Type :: enum i32 {
|
||||||
|
None = 0,
|
||||||
|
Other = 1,
|
||||||
|
Direct = 2,
|
||||||
|
IPv4_To_IPv6 = 11,
|
||||||
|
ISA_TAP = 13,
|
||||||
|
Teredo = 14,
|
||||||
|
IP_HTTPS = 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
Prefix_Origin :: enum i32 {
|
||||||
|
Other = 0,
|
||||||
|
Manual = 1,
|
||||||
|
Well_Known = 2,
|
||||||
|
DHCP = 3,
|
||||||
|
Router_Advertisement = 4,
|
||||||
|
Unchanged = 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
Suffix_Origin :: enum i32 {
|
||||||
|
Other = 0,
|
||||||
|
Manual = 1,
|
||||||
|
Well_Known = 2,
|
||||||
|
DHCP = 3,
|
||||||
|
Link_Layer_Address = 4,
|
||||||
|
Random = 5,
|
||||||
|
Unchanged = 16,
|
||||||
|
}
|
||||||
|
|
||||||
|
Address_Duplication :: enum i32 {
|
||||||
|
Invalid = 0,
|
||||||
|
Tentative = 1,
|
||||||
|
Duplicate = 2,
|
||||||
|
Deprecated = 3,
|
||||||
|
Preferred = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS DEFINITIONS
|
||||||
|
DNS_Configuration :: struct {
|
||||||
|
// Configuration files.
|
||||||
|
resolv_conf: string,
|
||||||
|
hosts_file: string,
|
||||||
|
|
||||||
|
// TODO: Allow loading these up with `reload_configuration()` call or the like,
|
||||||
|
// so we don't have to do it each call.
|
||||||
|
name_servers: []Endpoint,
|
||||||
|
hosts_file_entries: []DNS_Record,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Record_Type :: enum u16 {
|
||||||
|
DNS_TYPE_A = 0x1, // IP4 address.
|
||||||
|
DNS_TYPE_NS = 0x2, // IP6 address.
|
||||||
|
DNS_TYPE_CNAME = 0x5, // Another host name.
|
||||||
|
DNS_TYPE_MX = 0xf, // Arbitrary binary data or text.
|
||||||
|
DNS_TYPE_AAAA = 0x1c, // Address of a name (DNS) server.
|
||||||
|
DNS_TYPE_TEXT = 0x10, // Address and preference priority of a mail exchange server.
|
||||||
|
DNS_TYPE_SRV = 0x21, // Address, port, priority, and weight of a host that provides a particular service.
|
||||||
|
|
||||||
|
IP4 = DNS_TYPE_A,
|
||||||
|
IP6 = DNS_TYPE_AAAA,
|
||||||
|
CNAME = DNS_TYPE_CNAME,
|
||||||
|
TXT = DNS_TYPE_TEXT,
|
||||||
|
NS = DNS_TYPE_NS,
|
||||||
|
MX = DNS_TYPE_MX,
|
||||||
|
SRV = DNS_TYPE_SRV,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.
|
||||||
|
DNS_Record_Base :: struct {
|
||||||
|
record_name: string,
|
||||||
|
ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated.
|
||||||
|
}
|
||||||
|
|
||||||
|
// An IP4 address that the domain name maps to. There can be any number of these.
|
||||||
|
DNS_Record_IP4 :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
address: IP4_Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
// An IPv6 address that the domain name maps to. There can be any number of these.
|
||||||
|
DNS_Record_IP6 :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
address: IP6_Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Another domain name that the domain name maps to.
|
||||||
|
Domains can be pointed to another domain instead of directly to an IP address.
|
||||||
|
`get_dns_records` will recursively follow these if you request this type of record.
|
||||||
|
*/
|
||||||
|
DNS_Record_CNAME :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
host_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Arbitrary string data that is associated with the domain name.
|
||||||
|
Commonly of the form `key=value` to be parsed, though there is no specific format for them.
|
||||||
|
These can be used for any purpose.
|
||||||
|
*/
|
||||||
|
DNS_Record_TXT :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
value: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Domain names of other DNS servers that are associated with the domain name.
|
||||||
|
TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these.
|
||||||
|
*/
|
||||||
|
DNS_Record_NS :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
host_name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain names for email servers that are associated with the domain name.
|
||||||
|
// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred.
|
||||||
|
DNS_Record_MX :: struct {
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
host_name: string,
|
||||||
|
preference: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
An endpoint for a service that is available through the domain name.
|
||||||
|
This is the way to discover the services that a domain name provides.
|
||||||
|
|
||||||
|
Clients MUST attempt to contact the host with the lowest priority that they can reach.
|
||||||
|
If two hosts have the same priority, they should be contacted in the order according to their weight.
|
||||||
|
Hosts with larger weights should have a proportionally higher chance of being contacted by clients.
|
||||||
|
A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise).
|
||||||
|
|
||||||
|
The host may be "." to indicate that it is "decidedly not available" on this domain.
|
||||||
|
*/
|
||||||
|
DNS_Record_SRV :: struct {
|
||||||
|
// base contains the full name of this record.
|
||||||
|
// e.g: _sip._tls.example.com
|
||||||
|
using base: DNS_Record_Base,
|
||||||
|
|
||||||
|
// The hostname or address where this service can be found.
|
||||||
|
target: string,
|
||||||
|
// The port on which this service can be found.
|
||||||
|
port: int,
|
||||||
|
|
||||||
|
service_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||||
|
protocol_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||||
|
|
||||||
|
// Lower is higher priority
|
||||||
|
priority: int,
|
||||||
|
// Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight.
|
||||||
|
// The number of seconds that it will take to update the record.
|
||||||
|
weight: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Record :: union {
|
||||||
|
DNS_Record_IP4,
|
||||||
|
DNS_Record_IP6,
|
||||||
|
DNS_Record_CNAME,
|
||||||
|
DNS_Record_TXT,
|
||||||
|
DNS_Record_NS,
|
||||||
|
DNS_Record_MX,
|
||||||
|
DNS_Record_SRV,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Response_Code :: enum u16be {
|
||||||
|
No_Error,
|
||||||
|
Format_Error,
|
||||||
|
Server_Failure,
|
||||||
|
Name_Error,
|
||||||
|
Not_Implemented,
|
||||||
|
Refused,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Query :: enum u16be {
|
||||||
|
Host_Address = 1,
|
||||||
|
Authoritative_Name_Server = 2,
|
||||||
|
Mail_Destination = 3,
|
||||||
|
Mail_Forwarder = 4,
|
||||||
|
CNAME = 5,
|
||||||
|
All = 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Header :: struct {
|
||||||
|
id: u16be,
|
||||||
|
is_response: bool,
|
||||||
|
opcode: u16be,
|
||||||
|
is_authoritative: bool,
|
||||||
|
is_truncated: bool,
|
||||||
|
is_recursion_desired: bool,
|
||||||
|
is_recursion_available: bool,
|
||||||
|
response_code: DNS_Response_Code,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Record_Header :: struct #packed {
|
||||||
|
type: u16be,
|
||||||
|
class: u16be,
|
||||||
|
ttl: u32be,
|
||||||
|
length: u16be,
|
||||||
|
}
|
||||||
|
|
||||||
|
DNS_Host_Entry :: struct {
|
||||||
|
name: string,
|
||||||
|
addr: Address,
|
||||||
|
}
|
||||||
@@ -0,0 +1,863 @@
|
|||||||
|
// +build windows, linux, darwin
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:mem"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:time"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Default configuration for DNS resolution.
|
||||||
|
*/
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||||
|
resolv_conf = "",
|
||||||
|
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
|
||||||
|
}
|
||||||
|
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
|
||||||
|
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||||
|
resolv_conf = "/etc/resolv.conf",
|
||||||
|
hosts_file = "/etc/hosts",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#panic("Please add a configuration for this OS.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(init)
|
||||||
|
init_dns_configuration :: proc() {
|
||||||
|
/*
|
||||||
|
Resolve %ENVIRONMENT% placeholders in their paths.
|
||||||
|
*/
|
||||||
|
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
|
||||||
|
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy_dns_configuration :: proc() {
|
||||||
|
delete(dns_configuration.resolv_conf)
|
||||||
|
delete(dns_configuration.hosts_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_configuration := DEFAULT_DNS_CONFIGURATION
|
||||||
|
|
||||||
|
// Always allocates for consistency.
|
||||||
|
replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
|
||||||
|
// Nothing to replace. Return a clone of the original.
|
||||||
|
if strings.count(path, "%") != 2 {
|
||||||
|
return strings.clone(path, allocator), true
|
||||||
|
}
|
||||||
|
|
||||||
|
left := strings.index(path, "%") + 1
|
||||||
|
assert(left > 0 && left <= len(path)) // should be covered by there being two %
|
||||||
|
|
||||||
|
right := strings.index(path[left:], "%") + 1
|
||||||
|
assert(right > 0 && right <= len(path)) // should be covered by there being two %
|
||||||
|
|
||||||
|
env_key := path[left: right]
|
||||||
|
env_val := os.get_env(env_key, allocator)
|
||||||
|
defer delete(env_val)
|
||||||
|
|
||||||
|
res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
|
||||||
|
return res, true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Resolves a hostname to exactly one IP4 and IP6 endpoint.
|
||||||
|
It's then up to you which one you use.
|
||||||
|
Note that which address you use to open a socket, determines the type of the socket you get.
|
||||||
|
|
||||||
|
Returns `ok=false` if the host name could not be resolved to any endpoints.
|
||||||
|
|
||||||
|
Returned endpoints have the same port as provided in the string, or 0 if absent.
|
||||||
|
If you want to use a specific port, just modify the field after the call to this procedure.
|
||||||
|
|
||||||
|
If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped.
|
||||||
|
This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get
|
||||||
|
back an endpoint in both cases.
|
||||||
|
*/
|
||||||
|
resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {
|
||||||
|
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||||
|
switch t in target {
|
||||||
|
case Endpoint:
|
||||||
|
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||||
|
switch in t.address {
|
||||||
|
case IP4_Address: ep4 = t
|
||||||
|
case IP6_Address: ep6 = t
|
||||||
|
case: unreachable()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case Host:
|
||||||
|
err4, err6: Network_Error = ---, ---
|
||||||
|
ep4, err4 = resolve_ip4(t.hostname)
|
||||||
|
ep6, err6 = resolve_ip6(t.hostname)
|
||||||
|
if err4 != nil && err6 != nil {
|
||||||
|
err = err4
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) {
|
||||||
|
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||||
|
switch t in target {
|
||||||
|
case Endpoint:
|
||||||
|
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||||
|
switch in t.address {
|
||||||
|
case IP4_Address:
|
||||||
|
return t, nil
|
||||||
|
case IP6_Address:
|
||||||
|
err = .Unable_To_Resolve
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case Host:
|
||||||
|
recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
|
||||||
|
if len(recs) == 0 {
|
||||||
|
err = .Unable_To_Resolve
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ep4 = {
|
||||||
|
address = recs[0].(DNS_Record_IP4).address,
|
||||||
|
port = t.port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) {
|
||||||
|
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||||
|
switch t in target {
|
||||||
|
case Endpoint:
|
||||||
|
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||||
|
switch in t.address {
|
||||||
|
case IP4_Address:
|
||||||
|
err = .Unable_To_Resolve
|
||||||
|
return
|
||||||
|
case IP6_Address:
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
case Host:
|
||||||
|
recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
|
||||||
|
if len(recs) == 0 {
|
||||||
|
err = .Unable_To_Resolve
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ep6 = {
|
||||||
|
address = recs[0].(DNS_Record_IP6).address,
|
||||||
|
port = t.port,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Performs a recursive DNS query for records of a particular type for the hostname using the OS.
|
||||||
|
|
||||||
|
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||||
|
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||||
|
IP address is reached.
|
||||||
|
|
||||||
|
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||||
|
See `destroy_records`.
|
||||||
|
*/
|
||||||
|
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||||
|
return _get_dns_records_os(hostname, type, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A generic DNS client usable on any platform.
|
||||||
|
Performs a recursive DNS query for records of a particular type for the hostname.
|
||||||
|
|
||||||
|
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||||
|
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||||
|
IP address is reached.
|
||||||
|
|
||||||
|
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||||
|
See `destroy_records`.
|
||||||
|
*/
|
||||||
|
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
if type != .SRV {
|
||||||
|
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||||
|
ok := validate_hostname(hostname)
|
||||||
|
if !ok {
|
||||||
|
return nil, .Invalid_Hostname_Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr := DNS_Header{
|
||||||
|
id = 0,
|
||||||
|
is_response = false,
|
||||||
|
opcode = 0,
|
||||||
|
is_authoritative = false,
|
||||||
|
is_truncated = false,
|
||||||
|
is_recursion_desired = true,
|
||||||
|
is_recursion_available = false,
|
||||||
|
response_code = DNS_Response_Code.No_Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
id, bits := pack_dns_header(hdr)
|
||||||
|
dns_hdr := [6]u16be{}
|
||||||
|
dns_hdr[0] = id
|
||||||
|
dns_hdr[1] = bits
|
||||||
|
dns_hdr[2] = 1
|
||||||
|
|
||||||
|
dns_query := [2]u16be{ u16be(type), 1 }
|
||||||
|
|
||||||
|
output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{}
|
||||||
|
b := strings.builder_from_slice(output[:])
|
||||||
|
|
||||||
|
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
|
||||||
|
ok := encode_hostname(&b, hostname)
|
||||||
|
if !ok {
|
||||||
|
return nil, .Invalid_Hostname_Error
|
||||||
|
}
|
||||||
|
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
|
||||||
|
|
||||||
|
dns_packet := output[:strings.builder_len(b)]
|
||||||
|
|
||||||
|
dns_response_buf := [4096]u8{}
|
||||||
|
dns_response: []u8
|
||||||
|
for name_server in name_servers {
|
||||||
|
conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
|
||||||
|
if sock_err != nil {
|
||||||
|
return nil, .Connection_Error
|
||||||
|
}
|
||||||
|
defer close(conn)
|
||||||
|
|
||||||
|
_, send_err := send(conn, dns_packet[:], name_server)
|
||||||
|
if send_err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
set_err := set_option(conn, .Receive_Timeout, time.Second * 1)
|
||||||
|
if set_err != nil {
|
||||||
|
return nil, .Connection_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:])
|
||||||
|
if recv_err == UDP_Recv_Error.Timeout {
|
||||||
|
continue
|
||||||
|
} else if recv_err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if recv_sz == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_response = dns_response_buf[:recv_sz]
|
||||||
|
|
||||||
|
rsp, _ok := parse_response(dns_response, type)
|
||||||
|
if !_ok {
|
||||||
|
return nil, .Server_Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rsp) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return rsp[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// `records` slice is also destroyed.
|
||||||
|
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
for rec in records {
|
||||||
|
switch r in rec {
|
||||||
|
case DNS_Record_IP4:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
|
||||||
|
case DNS_Record_IP6:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
|
||||||
|
case DNS_Record_CNAME:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
delete(r.host_name)
|
||||||
|
|
||||||
|
case DNS_Record_TXT:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
delete(r.value)
|
||||||
|
|
||||||
|
case DNS_Record_NS:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
delete(r.host_name)
|
||||||
|
|
||||||
|
case DNS_Record_MX:
|
||||||
|
delete(r.base.record_name)
|
||||||
|
delete(r.host_name)
|
||||||
|
|
||||||
|
case DNS_Record_SRV:
|
||||||
|
delete(r.record_name)
|
||||||
|
delete(r.target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(records, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP
|
||||||
|
or is that what recursion desired does? Do we need to handle recursion unavailable?
|
||||||
|
How do we deal with is_authoritative / is_truncated?
|
||||||
|
*/
|
||||||
|
|
||||||
|
NAME_MAX :: 255
|
||||||
|
LABEL_MAX :: 63
|
||||||
|
|
||||||
|
pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) {
|
||||||
|
id = hdr.id
|
||||||
|
bits = hdr.opcode << 1 | u16be(hdr.response_code)
|
||||||
|
if hdr.is_response {
|
||||||
|
bits |= 1 << 15
|
||||||
|
}
|
||||||
|
if hdr.is_authoritative {
|
||||||
|
bits |= 1 << 10
|
||||||
|
}
|
||||||
|
if hdr.is_truncated {
|
||||||
|
bits |= 1 << 9
|
||||||
|
}
|
||||||
|
if hdr.is_recursion_desired {
|
||||||
|
bits |= 1 << 8
|
||||||
|
}
|
||||||
|
if hdr.is_recursion_available {
|
||||||
|
bits |= 1 << 7
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, bits
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
|
||||||
|
hdr.id = id
|
||||||
|
hdr.is_response = (bits & (1 << 15)) != 0
|
||||||
|
hdr.opcode = (bits >> 11) & 0xF
|
||||||
|
hdr.is_authoritative = (bits & (1 << 10)) != 0
|
||||||
|
hdr.is_truncated = (bits & (1 << 9)) != 0
|
||||||
|
hdr.is_recursion_desired = (bits & (1 << 8)) != 0
|
||||||
|
hdr.is_recursion_available = (bits & (1 << 7)) != 0
|
||||||
|
hdr.response_code = DNS_Response_Code(bits & 0xF)
|
||||||
|
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
|
||||||
|
defer delete(res)
|
||||||
|
resolv_str := string(res)
|
||||||
|
|
||||||
|
_name_servers := make([dynamic]Endpoint, 0, allocator)
|
||||||
|
for line in strings.split_lines_iterator(&resolv_str) {
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
id_str := "nameserver"
|
||||||
|
if strings.compare(line[:len(id_str)], id_str) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
server_ip_str := strings.trim_left_space(line[len(id_str):])
|
||||||
|
if len(server_ip_str) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := parse_address(server_ip_str)
|
||||||
|
endpoint := Endpoint{
|
||||||
|
addr,
|
||||||
|
53,
|
||||||
|
}
|
||||||
|
append(&_name_servers, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _name_servers[:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
|
||||||
|
defer delete(res)
|
||||||
|
|
||||||
|
_hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
|
||||||
|
hosts_str := string(res)
|
||||||
|
for line in strings.split_lines_iterator(&hosts_str) {
|
||||||
|
if len(line) == 0 || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
splits := strings.fields(line)
|
||||||
|
defer delete(splits)
|
||||||
|
|
||||||
|
ip_str := splits[0]
|
||||||
|
addr := parse_address(ip_str)
|
||||||
|
if addr == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for hostname in splits[1:] {
|
||||||
|
if len(hostname) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&_hosts, DNS_Host_Entry{hostname, addr})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _hosts[:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// www.google.com -> 3www6google3com0
|
||||||
|
encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) {
|
||||||
|
_hostname := hostname
|
||||||
|
for section in strings.split_iterator(&_hostname, ".") {
|
||||||
|
if len(section) > LABEL_MAX {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strings.write_byte(b, u8(len(section)))
|
||||||
|
strings.write_string(b, section)
|
||||||
|
}
|
||||||
|
strings.write_byte(b, 0)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) {
|
||||||
|
out_size := 0
|
||||||
|
|
||||||
|
cur_idx := start_idx
|
||||||
|
iteration_max := 0
|
||||||
|
top: for cur_idx < len(packet) {
|
||||||
|
if packet[cur_idx] == 0 {
|
||||||
|
out_size += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if iteration_max > 255 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packet[cur_idx] {
|
||||||
|
case 0xC0:
|
||||||
|
out_size += 2
|
||||||
|
break top
|
||||||
|
case:
|
||||||
|
label_size := int(packet[cur_idx]) + 1
|
||||||
|
idx2 := cur_idx + label_size
|
||||||
|
|
||||||
|
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out_size += label_size
|
||||||
|
cur_idx = idx2
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration_max += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if start_idx + out_size > len(packet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return out_size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {
|
||||||
|
output := [NAME_MAX]u8{}
|
||||||
|
b := strings.builder_from_slice(output[:])
|
||||||
|
|
||||||
|
// If you're on level 0, update out_bytes, everything through a pointer
|
||||||
|
// doesn't count towards this hostname's packet length
|
||||||
|
|
||||||
|
// Evaluate tokens to generate the hostname
|
||||||
|
out_size := 0
|
||||||
|
level := 0
|
||||||
|
print_size := 0
|
||||||
|
cur_idx := start_idx
|
||||||
|
iteration_max := 0
|
||||||
|
labels_added := 0
|
||||||
|
for cur_idx < len(packet) {
|
||||||
|
if packet[cur_idx] == 0 {
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
out_size += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if iteration_max > 255 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch packet[cur_idx] {
|
||||||
|
|
||||||
|
// This is a offset to more data in the packet, jump to it
|
||||||
|
case 0xC0:
|
||||||
|
pkt := packet[cur_idx:cur_idx+2]
|
||||||
|
val := (^u16be)(raw_data(pkt))^
|
||||||
|
offset := int(val & 0x3FFF)
|
||||||
|
if offset > len(packet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_idx = offset
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
out_size += 2
|
||||||
|
level += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a label, insert it into the hostname
|
||||||
|
case:
|
||||||
|
label_size := int(packet[cur_idx])
|
||||||
|
idx2 := cur_idx + label_size + 1
|
||||||
|
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if print_size + label_size + 1 > NAME_MAX {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if labels_added > 0 {
|
||||||
|
strings.write_byte(&b, '.')
|
||||||
|
}
|
||||||
|
strings.write_bytes(&b, packet[cur_idx+1:idx2])
|
||||||
|
print_size += label_size + 1
|
||||||
|
labels_added += 1
|
||||||
|
|
||||||
|
cur_idx = idx2
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
out_size += label_size + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration_max += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if start_idx + out_size > len(packet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.clone(strings.to_string(b), allocator), out_size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uses RFC 952 & RFC 1123
|
||||||
|
validate_hostname :: proc(hostname: string) -> (ok: bool) {
|
||||||
|
if len(hostname) > 255 || len(hostname) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname[0] == '-' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_hostname := hostname
|
||||||
|
for label in strings.split_iterator(&_hostname, ".") {
|
||||||
|
if len(label) > 63 || len(label) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for ch in label {
|
||||||
|
switch ch {
|
||||||
|
case:
|
||||||
|
return
|
||||||
|
case 'a'..='z', 'A'..='Z', '0'..='9', '-':
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) {
|
||||||
|
record_buf := packet[cur_off^:]
|
||||||
|
|
||||||
|
srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return
|
||||||
|
// TODO(tetra): Not sure what we should call this.
|
||||||
|
// Is it really only used in SRVs?
|
||||||
|
// Maybe some refactoring is required?
|
||||||
|
|
||||||
|
ahdr_sz := size_of(DNS_Record_Header)
|
||||||
|
if len(record_buf) - hn_sz < ahdr_sz {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz]
|
||||||
|
record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes)
|
||||||
|
|
||||||
|
data_sz := record_hdr.length
|
||||||
|
data_off := cur_off^ + int(hn_sz) + int(ahdr_sz)
|
||||||
|
data := packet[data_off:data_off+int(data_sz)]
|
||||||
|
cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz)
|
||||||
|
|
||||||
|
if u16be(filter) != record_hdr.type {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
_record: DNS_Record
|
||||||
|
#partial switch DNS_Record_Type(record_hdr.type) {
|
||||||
|
case .IP4:
|
||||||
|
if len(data) != 4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := (^IP4_Address)(raw_data(data))^
|
||||||
|
|
||||||
|
_record = DNS_Record_IP4{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
address = addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
case .IP6:
|
||||||
|
if len(data) != 16 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := (^IP6_Address)(raw_data(data))^
|
||||||
|
|
||||||
|
_record = DNS_Record_IP6{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
address = addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
case .CNAME:
|
||||||
|
hostname, _ := decode_hostname(packet, data_off) or_return
|
||||||
|
|
||||||
|
_record = DNS_Record_CNAME{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
host_name = hostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
case .TXT:
|
||||||
|
_record = DNS_Record_TXT{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
value = strings.clone(string(data)),
|
||||||
|
}
|
||||||
|
|
||||||
|
case .NS:
|
||||||
|
name, _ := decode_hostname(packet, data_off) or_return
|
||||||
|
|
||||||
|
_record = DNS_Record_NS{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
host_name = name,
|
||||||
|
}
|
||||||
|
|
||||||
|
case .SRV:
|
||||||
|
if len(data) <= 6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_data := mem.slice_data_cast([]u16be, data)
|
||||||
|
|
||||||
|
priority, weight, port := _data[0], _data[1], _data[2]
|
||||||
|
target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return
|
||||||
|
|
||||||
|
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||||
|
// The record name is the name of the record.
|
||||||
|
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||||
|
// by making this request in the first place.
|
||||||
|
|
||||||
|
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||||
|
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||||
|
|
||||||
|
parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
service_name, protocol_name := parts[0], parts[1]
|
||||||
|
|
||||||
|
_record = DNS_Record_SRV{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
target = target,
|
||||||
|
service_name = service_name,
|
||||||
|
protocol_name = protocol_name,
|
||||||
|
priority = int(priority),
|
||||||
|
weight = int(weight),
|
||||||
|
port = int(port),
|
||||||
|
}
|
||||||
|
|
||||||
|
case .MX:
|
||||||
|
if len(data) <= 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preference: u16be = mem.slice_data_cast([]u16be, data)[0]
|
||||||
|
hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return
|
||||||
|
|
||||||
|
_record = DNS_Record_MX{
|
||||||
|
base = DNS_Record_Base{
|
||||||
|
record_name = strings.clone(srv_record_name),
|
||||||
|
ttl_seconds = u32(record_hdr.ttl),
|
||||||
|
},
|
||||||
|
host_name = hostname,
|
||||||
|
preference = int(preference),
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return _record, true
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
DNS Query Response Format:
|
||||||
|
- DNS_Header (packed)
|
||||||
|
- Query Count
|
||||||
|
- Answer Count
|
||||||
|
- Authority Count
|
||||||
|
- Additional Count
|
||||||
|
- Query[]
|
||||||
|
- Hostname -- encoded
|
||||||
|
- Type
|
||||||
|
- Class
|
||||||
|
- Answer[]
|
||||||
|
- DNS Record Data
|
||||||
|
- Authority[]
|
||||||
|
- DNS Record Data
|
||||||
|
- Additional[]
|
||||||
|
- DNS Record Data
|
||||||
|
|
||||||
|
DNS Record Data:
|
||||||
|
- DNS_Record_Header
|
||||||
|
- Data[]
|
||||||
|
*/
|
||||||
|
|
||||||
|
parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
HEADER_SIZE_BYTES :: 12
|
||||||
|
if len(response) < HEADER_SIZE_BYTES {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_records := make([dynamic]DNS_Record, 0)
|
||||||
|
|
||||||
|
dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES])
|
||||||
|
hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
|
||||||
|
if !hdr.is_response {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
question_count := int(dns_hdr_chunks[2])
|
||||||
|
if question_count != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
answer_count := int(dns_hdr_chunks[3])
|
||||||
|
authority_count := int(dns_hdr_chunks[4])
|
||||||
|
additional_count := int(dns_hdr_chunks[5])
|
||||||
|
|
||||||
|
cur_idx := HEADER_SIZE_BYTES
|
||||||
|
|
||||||
|
for _ in 0..<question_count {
|
||||||
|
if cur_idx == len(response) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dq_sz :: 4
|
||||||
|
hn_sz := skip_hostname(response, cur_idx) or_return
|
||||||
|
dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz])
|
||||||
|
|
||||||
|
cur_idx += hn_sz + dq_sz
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<answer_count {
|
||||||
|
if cur_idx == len(response) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := parse_record(response, &cur_idx, filter) or_return
|
||||||
|
if rec == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&_records, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<authority_count {
|
||||||
|
if cur_idx == len(response) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := parse_record(response, &cur_idx, filter) or_return
|
||||||
|
if rec == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&_records, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<additional_count {
|
||||||
|
if cur_idx == len(response) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := parse_record(response, &cur_idx, filter) or_return
|
||||||
|
if rec == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
append(&_records, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _records[:], true
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//+build linux, darwin
|
||||||
|
package net
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
if type != .SRV {
|
||||||
|
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||||
|
ok := validate_hostname(hostname)
|
||||||
|
if !ok {
|
||||||
|
return nil, .Invalid_Hostname_Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name_servers, resolve_ok := load_resolv_conf(dns_configuration.resolv_conf)
|
||||||
|
defer delete(name_servers)
|
||||||
|
if !resolve_ok {
|
||||||
|
return nil, .Invalid_Resolv_Config_Error
|
||||||
|
}
|
||||||
|
if len(name_servers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
|
||||||
|
defer delete(hosts)
|
||||||
|
if !hosts_ok {
|
||||||
|
return nil, .Invalid_Hosts_Config_Error
|
||||||
|
}
|
||||||
|
if len(hosts) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
host_overrides := make([dynamic]DNS_Record)
|
||||||
|
for host in hosts {
|
||||||
|
if strings.compare(host.name, hostname) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if type == .IP4 && family_from_address(host.addr) == .IP4 {
|
||||||
|
record := DNS_Record_IP4{
|
||||||
|
base = {
|
||||||
|
record_name = strings.clone(hostname),
|
||||||
|
ttl_seconds = 0,
|
||||||
|
},
|
||||||
|
address = host.addr.(IP4_Address),
|
||||||
|
}
|
||||||
|
append(&host_overrides, record)
|
||||||
|
} else if type == .IP6 && family_from_address(host.addr) == .IP6 {
|
||||||
|
record := DNS_Record_IP6{
|
||||||
|
base = {
|
||||||
|
record_name = strings.clone(hostname),
|
||||||
|
ttl_seconds = 0,
|
||||||
|
},
|
||||||
|
address = host.addr.(IP6_Address),
|
||||||
|
}
|
||||||
|
append(&host_overrides, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(host_overrides) > 0 {
|
||||||
|
return host_overrides[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
//+build windows
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
import win "core:sys/windows"
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
|
||||||
|
rec: ^win.DNS_RECORD
|
||||||
|
res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil)
|
||||||
|
|
||||||
|
switch u32(res) {
|
||||||
|
case 0:
|
||||||
|
// okay
|
||||||
|
case win.ERROR_INVALID_NAME:
|
||||||
|
return nil, .Invalid_Hostname_Error
|
||||||
|
case win.DNS_INFO_NO_RECORDS:
|
||||||
|
return
|
||||||
|
case:
|
||||||
|
return nil, .System_Error
|
||||||
|
}
|
||||||
|
defer win.DnsRecordListFree(rec, 1) // 1 means that we're freeing a list... because the proc name isn't enough.
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
for r := rec; r != nil; r = r.pNext {
|
||||||
|
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
recs := make([dynamic]DNS_Record, 0, count)
|
||||||
|
if recs == nil do return nil, .System_Error // return no results if OOM.
|
||||||
|
|
||||||
|
for r := rec; r != nil; r = r.pNext {
|
||||||
|
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||||
|
|
||||||
|
base_record := DNS_Record_Base{
|
||||||
|
record_name = strings.clone(string(r.pName)),
|
||||||
|
ttl_seconds = r.dwTtl,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch DNS_Record_Type(r.wType) {
|
||||||
|
case .IP4:
|
||||||
|
addr := IP4_Address(transmute([4]u8)r.Data.A)
|
||||||
|
record := DNS_Record_IP4{
|
||||||
|
base = base_record,
|
||||||
|
address = addr,
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
|
||||||
|
case .IP6:
|
||||||
|
addr := IP6_Address(transmute([8]u16be) r.Data.AAAA)
|
||||||
|
record := DNS_Record_IP6{
|
||||||
|
base = base_record,
|
||||||
|
address = addr,
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
|
||||||
|
case .CNAME:
|
||||||
|
|
||||||
|
hostname := strings.clone(string(r.Data.CNAME))
|
||||||
|
record := DNS_Record_CNAME{
|
||||||
|
base = base_record,
|
||||||
|
host_name = hostname,
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
|
||||||
|
case .TXT:
|
||||||
|
n := r.Data.TXT.dwStringCount
|
||||||
|
ptr := &r.Data.TXT.pStringArray
|
||||||
|
c_strs := mem.slice_ptr(ptr, int(n))
|
||||||
|
|
||||||
|
for cstr in c_strs {
|
||||||
|
record := DNS_Record_TXT{
|
||||||
|
base = base_record,
|
||||||
|
value = strings.clone(string(cstr)),
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .NS:
|
||||||
|
hostname := strings.clone(string(r.Data.NS))
|
||||||
|
record := DNS_Record_NS{
|
||||||
|
base = base_record,
|
||||||
|
host_name = hostname,
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
|
||||||
|
case .MX:
|
||||||
|
/*
|
||||||
|
TODO(tetra): Order by preference priority? (Prefer hosts with lower preference values.)
|
||||||
|
Or maybe not because you're supposed to just use the first one that works
|
||||||
|
and which order they're in changes every few calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
record := DNS_Record_MX{
|
||||||
|
base = base_record,
|
||||||
|
host_name = strings.clone(string(r.Data.MX.pNameExchange)),
|
||||||
|
preference = int(r.Data.MX.wPreference),
|
||||||
|
}
|
||||||
|
append(&recs, record)
|
||||||
|
|
||||||
|
case .SRV:
|
||||||
|
target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on
|
||||||
|
priority := int(r.Data.SRV.wPriority)
|
||||||
|
weight := int(r.Data.SRV.wWeight)
|
||||||
|
port := int(r.Data.SRV.wPort)
|
||||||
|
|
||||||
|
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||||
|
// The record name is the name of the record.
|
||||||
|
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||||
|
// by making this request in the first place.
|
||||||
|
|
||||||
|
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||||
|
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||||
|
|
||||||
|
parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
service_name, protocol_name := parts[0], parts[1]
|
||||||
|
|
||||||
|
append(&recs, DNS_Record_SRV {
|
||||||
|
base = base_record,
|
||||||
|
target = target,
|
||||||
|
port = port,
|
||||||
|
service_name = service_name,
|
||||||
|
protocol_name = protocol_name,
|
||||||
|
priority = priority,
|
||||||
|
weight = weight,
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
records = recs[:]
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Supports Windows, Linux and OSX.
|
||||||
|
- Opening and closing of TCP and UDP sockets.
|
||||||
|
- Sending to and receiving from these sockets.
|
||||||
|
- DNS name lookup, using either the OS or our own resolver.
|
||||||
|
|
||||||
|
Planned:
|
||||||
|
- Nonblocking IO
|
||||||
|
- `Connection` struct
|
||||||
|
A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle.
|
||||||
|
- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures.
|
||||||
|
- Use `context.temp_allocator` instead of stack-based arenas?
|
||||||
|
And check it's the default temp allocator or can give us 4 MiB worth of memory
|
||||||
|
without punting to the main allocator by comparing their addresses in an @(init) procedure.
|
||||||
|
Panic if this assumption is not met.
|
||||||
|
|
||||||
|
- Document assumptions about libc usage (or avoidance thereof) for each platform.
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
- For performance reasons this package relies on the `context.temp_allocator` in some places.
|
||||||
|
|
||||||
|
You can replace the default `context.temp_allocator` with your own as long as it meets
|
||||||
|
this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed.
|
||||||
|
|
||||||
|
If this expectation is not met, the package's @(init) procedure will attempt to detect
|
||||||
|
this and panic to avoid temp allocations prematurely overwriting data and garbling results,
|
||||||
|
or worse. This means that should you replace the temp allocator with an insufficient one,
|
||||||
|
we'll do our best to loudly complain the first time you try it.
|
||||||
|
*/
|
||||||
|
package net
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package net
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
Create_Socket_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||||
|
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||||
|
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||||
|
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||||
|
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||||
|
}
|
||||||
|
|
||||||
|
Dial_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Port_Required = -1,
|
||||||
|
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE),
|
||||||
|
In_Progress = c.int(os.EINPROGRESS),
|
||||||
|
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||||
|
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||||
|
Refused = c.int(os.ECONNREFUSED),
|
||||||
|
Is_Listening_Socket = c.int(os.EACCES),
|
||||||
|
Already_Connected = c.int(os.EISCONN),
|
||||||
|
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Timeout = c.int(os.ETIMEDOUT),
|
||||||
|
|
||||||
|
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
Would_Block = c.int(os.EWOULDBLOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||||
|
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||||
|
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||||
|
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||||
|
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||||
|
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||||
|
}
|
||||||
|
|
||||||
|
Listen_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE),
|
||||||
|
Already_Connected = c.int(os.EISCONN),
|
||||||
|
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||||
|
}
|
||||||
|
|
||||||
|
Accept_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
|
||||||
|
Reset = c.int(os.ECONNRESET),
|
||||||
|
Not_Listening = c.int(os.EINVAL),
|
||||||
|
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||||
|
|
||||||
|
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
Would_Block = c.int(os.EWOULDBLOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Shutdown = c.int(os.ESHUTDOWN),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
|
||||||
|
// TODO(tetra): Is this error actually possible here?
|
||||||
|
Connection_Broken = c.int(os.ENETRESET),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
|
||||||
|
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||||
|
Connection_Closed = c.int(os.ECONNRESET),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||||
|
Interrupted = c.int(os.EINTR),
|
||||||
|
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
UDP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated.
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||||
|
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||||
|
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
|
||||||
|
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
TCP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// TODO: merge with other errors?
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
Connection_Closed = c.int(os.ECONNRESET),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
Shutdown = c.int(os.ESHUTDOWN),
|
||||||
|
|
||||||
|
// The send queue was full.
|
||||||
|
// This is usually a transient issue.
|
||||||
|
//
|
||||||
|
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||||
|
// doesn't fit in the send queue.
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||||
|
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
UDP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||||
|
|
||||||
|
// TODO: not sure what the exact circumstances for this is yet
|
||||||
|
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||||
|
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||||
|
|
||||||
|
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||||
|
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||||
|
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
|
||||||
|
// The send queue was full.
|
||||||
|
// This is usually a transient issue.
|
||||||
|
//
|
||||||
|
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||||
|
// doesn't fit in the send queue.
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Manner :: enum c.int {
|
||||||
|
Receive = c.int(os.SHUT_RD),
|
||||||
|
Send = c.int(os.SHUT_WR),
|
||||||
|
Both = c.int(os.SHUT_RDWR),
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
Reset = c.int(os.ECONNRESET),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Invalid_Manner = c.int(os.EINVAL),
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket_Option_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||||
|
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||||
|
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
Set_Blocking_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// TODO: Add errors for `set_blocking`
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package net
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
Create_Socket_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||||
|
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||||
|
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||||
|
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||||
|
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||||
|
}
|
||||||
|
|
||||||
|
Dial_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Port_Required = -1,
|
||||||
|
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE),
|
||||||
|
In_Progress = c.int(os.EINPROGRESS),
|
||||||
|
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||||
|
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||||
|
Refused = c.int(os.ECONNREFUSED),
|
||||||
|
Is_Listening_Socket = c.int(os.EACCES),
|
||||||
|
Already_Connected = c.int(os.EISCONN),
|
||||||
|
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Timeout = c.int(os.ETIMEDOUT),
|
||||||
|
|
||||||
|
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
Would_Block = c.int(os.EWOULDBLOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||||
|
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||||
|
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||||
|
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||||
|
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||||
|
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||||
|
}
|
||||||
|
|
||||||
|
Listen_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = c.int(os.EADDRINUSE),
|
||||||
|
Already_Connected = c.int(os.EISCONN),
|
||||||
|
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||||
|
}
|
||||||
|
|
||||||
|
Accept_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Not_Listening = c.int(os.EINVAL),
|
||||||
|
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||||
|
|
||||||
|
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
Would_Block = c.int(os.EWOULDBLOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Shutdown = c.int(os.ESHUTDOWN),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
Connection_Broken = c.int(os.ENETRESET),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
|
||||||
|
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||||
|
Connection_Closed = c.int(os.ECONNRESET),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||||
|
Interrupted = c.int(os.EINTR),
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
}
|
||||||
|
|
||||||
|
UDP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// The buffer is too small to fit the entire message, and the message was truncated.
|
||||||
|
// When this happens, the rest of message is lost.
|
||||||
|
Buffer_Too_Small = c.int(os.EMSGSIZE),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||||
|
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||||
|
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
|
||||||
|
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
TCP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
// TODO(tetra): merge with other errors?
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
Connection_Closed = c.int(os.ECONNRESET),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
Shutdown = c.int(os.ESHUTDOWN),
|
||||||
|
|
||||||
|
// The send queue was full.
|
||||||
|
// This is usually a transient issue.
|
||||||
|
//
|
||||||
|
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||||
|
// doesn't fit in the send queue.
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Host_Unreachable = c.int(os.EHOSTUNREACH), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
Interrupted = c.int(os.EINTR), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
UDP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Message_Too_Long = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||||
|
|
||||||
|
// TODO: not sure what the exact circumstances for this is yet
|
||||||
|
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||||
|
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||||
|
|
||||||
|
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||||
|
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||||
|
Timeout = c.int(os.EWOULDBLOCK),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||||
|
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||||
|
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||||
|
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||||
|
|
||||||
|
// The send queue was full.
|
||||||
|
// This is usually a transient issue.
|
||||||
|
//
|
||||||
|
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||||
|
// doesn't fit in the send queue.
|
||||||
|
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||||
|
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Manner :: enum c.int {
|
||||||
|
Receive = c.int(os.SHUT_RD),
|
||||||
|
Send = c.int(os.SHUT_WR),
|
||||||
|
Both = c.int(os.SHUT_RDWR),
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Aborted = c.int(os.ECONNABORTED),
|
||||||
|
Reset = c.int(os.ECONNRESET),
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Not_Connected = c.int(os.ENOTCONN),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
Invalid_Manner = c.int(os.EINVAL),
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket_Option_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Offline = c.int(os.ENETDOWN),
|
||||||
|
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||||
|
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||||
|
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||||
|
Not_Socket = c.int(os.ENOTSOCK),
|
||||||
|
}
|
||||||
|
|
||||||
|
Set_Blocking_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// TODO: add errors occuring on followig calls:
|
||||||
|
// flags, _ := os.fcntl(sd, os.F_GETFL, 0)
|
||||||
|
// os.fcntl(sd, os.F_SETFL, flags | int(os.O_NONBLOCK))
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package net
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import win "core:sys/windows"
|
||||||
|
|
||||||
|
Create_Socket_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
|
||||||
|
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
|
||||||
|
Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
|
||||||
|
Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
|
||||||
|
}
|
||||||
|
|
||||||
|
Dial_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Port_Required = -1,
|
||||||
|
Address_In_Use = win.WSAEADDRINUSE,
|
||||||
|
In_Progress = win.WSAEALREADY,
|
||||||
|
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
|
||||||
|
Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
|
||||||
|
Refused = win.WSAECONNREFUSED,
|
||||||
|
Is_Listening_Socket = win.WSAEINVAL,
|
||||||
|
Already_Connected = win.WSAEISCONN,
|
||||||
|
Network_Unreachable = win.WSAENETUNREACH, // Device is offline
|
||||||
|
Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
Timeout = win.WSAETIMEDOUT,
|
||||||
|
Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
}
|
||||||
|
|
||||||
|
Bind_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
|
||||||
|
Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
|
||||||
|
Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||||
|
Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
|
||||||
|
Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
|
||||||
|
No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
|
||||||
|
}
|
||||||
|
|
||||||
|
Listen_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Address_In_Use = win.WSAEADDRINUSE,
|
||||||
|
Already_Connected = win.WSAEISCONN,
|
||||||
|
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Nonlocal_Address = win.WSAEADDRNOTAVAIL,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
|
||||||
|
}
|
||||||
|
|
||||||
|
Accept_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Not_Listening = win.WSAEINVAL,
|
||||||
|
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
|
||||||
|
|
||||||
|
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||||
|
Would_Block = win.WSAEWOULDBLOCK,
|
||||||
|
}
|
||||||
|
|
||||||
|
TCP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
Not_Connected = win.WSAENOTCONN,
|
||||||
|
Bad_Buffer = win.WSAEFAULT,
|
||||||
|
Keepalive_Failure = win.WSAENETRESET,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
Shutdown = win.WSAESHUTDOWN,
|
||||||
|
Would_Block = win.WSAEWOULDBLOCK,
|
||||||
|
|
||||||
|
// TODO: not functionally different from Reset; merge?
|
||||||
|
Aborted = win.WSAECONNABORTED,
|
||||||
|
Timeout = win.WSAETIMEDOUT,
|
||||||
|
|
||||||
|
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||||
|
Connection_Closed = win.WSAECONNRESET,
|
||||||
|
|
||||||
|
// TODO: verify can actually happen
|
||||||
|
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||||
|
}
|
||||||
|
|
||||||
|
UDP_Recv_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
|
||||||
|
// TODO: not functionally different from Reset; merge?
|
||||||
|
// UDP packets are limited in size, and the length of the incoming message exceeded it.
|
||||||
|
Aborted = win.WSAECONNABORTED,
|
||||||
|
Truncated = win.WSAEMSGSIZE,
|
||||||
|
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||||
|
Shutdown = win.WSAESHUTDOWN,
|
||||||
|
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||||
|
Bad_Buffer = win.WSAEFAULT,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||||
|
Would_Block = win.WSAEWOULDBLOCK,
|
||||||
|
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||||
|
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||||
|
Timeout = win.WSAETIMEDOUT,
|
||||||
|
|
||||||
|
// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
|
||||||
|
Incorrectly_Configured = win.WSAEINVAL,
|
||||||
|
TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider merging some errors to make handling them easier
|
||||||
|
// TODO: verify once more what errors to actually expose
|
||||||
|
TCP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// TODO: not functionally different from Reset; merge?
|
||||||
|
Aborted = win.WSAECONNABORTED,
|
||||||
|
Not_Connected = win.WSAENOTCONN,
|
||||||
|
Shutdown = win.WSAESHUTDOWN,
|
||||||
|
Connection_Closed = win.WSAECONNRESET,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||||
|
|
||||||
|
// TODO: verify possible, as not mentioned in docs
|
||||||
|
Offline = win.WSAENETUNREACH,
|
||||||
|
Timeout = win.WSAETIMEDOUT,
|
||||||
|
|
||||||
|
// A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||||
|
Broadcast_Disabled = win.WSAEACCES,
|
||||||
|
Bad_Buffer = win.WSAEFAULT,
|
||||||
|
|
||||||
|
// Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||||
|
Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
|
||||||
|
Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
|
||||||
|
}
|
||||||
|
|
||||||
|
UDP_Send_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
|
||||||
|
// TODO: not functionally different from Reset; merge?
|
||||||
|
Aborted = win.WSAECONNABORTED, // UDP packets are limited in size, and len(buf) exceeded it.
|
||||||
|
Message_Too_Long = win.WSAEMSGSIZE, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||||
|
Remote_Not_Listening = win.WSAECONNRESET,
|
||||||
|
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||||
|
Broadcast_Disabled = win.WSAEACCES,
|
||||||
|
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||||
|
|
||||||
|
// TODO: not functionally different from Reset; merge?
|
||||||
|
Keepalive_Failure = win.WSAENETRESET,
|
||||||
|
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||||
|
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||||
|
|
||||||
|
// This socket is unidirectional and cannot be used to send any data.
|
||||||
|
// TODO: verify possible; decide whether to keep if not
|
||||||
|
Receive_Only = win.WSAEOPNOTSUPP,
|
||||||
|
Would_Block = win.WSAEWOULDBLOCK,
|
||||||
|
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||||
|
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
|
||||||
|
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
|
||||||
|
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||||
|
Timeout = win.WSAETIMEDOUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Manner :: enum c.int {
|
||||||
|
Receive = win.SD_RECEIVE,
|
||||||
|
Send = win.SD_SEND,
|
||||||
|
Both = win.SD_BOTH,
|
||||||
|
}
|
||||||
|
|
||||||
|
Shutdown_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Aborted = win.WSAECONNABORTED,
|
||||||
|
Reset = win.WSAECONNRESET,
|
||||||
|
Offline = win.WSAENETDOWN,
|
||||||
|
Not_Connected = win.WSAENOTCONN,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
Invalid_Manner = win.WSAEINVAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket_Option :: enum c.int {
|
||||||
|
// bool: Whether the address that this socket is bound to can be reused by other sockets.
|
||||||
|
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
|
||||||
|
Reuse_Address = win.SO_REUSEADDR,
|
||||||
|
|
||||||
|
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
|
||||||
|
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
|
||||||
|
|
||||||
|
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
|
||||||
|
Keep_Alive = win.SO_KEEPALIVE,
|
||||||
|
|
||||||
|
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
|
||||||
|
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
|
||||||
|
|
||||||
|
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
|
||||||
|
Dont_Linger = win.SO_DONTLINGER,
|
||||||
|
|
||||||
|
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
|
||||||
|
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
|
||||||
|
|
||||||
|
// bool: When true, disables send-coalescing, therefore reducing latency.
|
||||||
|
TCP_Nodelay = win.TCP_NODELAY,
|
||||||
|
|
||||||
|
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
|
||||||
|
// is some remaining data waiting to be sent, and net.close() is called.
|
||||||
|
Linger = win.SO_LINGER,
|
||||||
|
|
||||||
|
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
|
||||||
|
Receive_Buffer_Size = win.SO_RCVBUF,
|
||||||
|
|
||||||
|
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
|
||||||
|
Send_Buffer_Size = win.SO_SNDBUF,
|
||||||
|
|
||||||
|
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
|
||||||
|
// For non-blocking sockets, ignored.
|
||||||
|
// Use a value of zero to potentially wait forever.
|
||||||
|
Receive_Timeout = win.SO_RCVTIMEO,
|
||||||
|
|
||||||
|
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
|
||||||
|
// For non-blocking sockets, ignored.
|
||||||
|
// Use a value of zero to potentially wait forever.
|
||||||
|
Send_Timeout = win.SO_SNDTIMEO,
|
||||||
|
|
||||||
|
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
|
||||||
|
Broadcast = win.SO_BROADCAST,
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket_Option_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
Linger_Only_Supports_Whole_Seconds = 1,
|
||||||
|
|
||||||
|
// The given value is too big or small to be given to the OS.
|
||||||
|
Value_Out_Of_Range,
|
||||||
|
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
Timeout_When_Keepalive_Set = win.WSAENETRESET,
|
||||||
|
Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
|
||||||
|
Reset_When_Keepalive_Set = win.WSAENOTCONN,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
}
|
||||||
|
|
||||||
|
Set_Blocking_Error :: enum c.int {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||||
|
Blocking_Call_In_Progress = win.WSAEINPROGRESS,
|
||||||
|
Not_Socket = win.WSAENOTSOCK,
|
||||||
|
|
||||||
|
// TODO: are those errors possible?
|
||||||
|
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
|
||||||
|
Invalid_Argument_Pointer = win.WSAEFAULT,
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// +build windows, linux, darwin
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
MAX_INTERFACE_ENUMERATION_TRIES :: 3
|
||||||
|
|
||||||
|
/*
|
||||||
|
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
|
||||||
|
*/
|
||||||
|
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||||
|
return _enumerate_interfaces(allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
`destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`.
|
||||||
|
*/
|
||||||
|
destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
for i in interfaces {
|
||||||
|
delete(i.adapter_name)
|
||||||
|
delete(i.friendly_name)
|
||||||
|
delete(i.description)
|
||||||
|
delete(i.dns_suffix)
|
||||||
|
|
||||||
|
delete(i.physical_address)
|
||||||
|
|
||||||
|
delete(i.unicast)
|
||||||
|
delete(i.multicast)
|
||||||
|
delete(i.anycast)
|
||||||
|
delete(i.gateways)
|
||||||
|
}
|
||||||
|
delete(interfaces, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string.
|
||||||
|
*/
|
||||||
|
physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
MAC_HEX := "0123456789ABCDEF"
|
||||||
|
|
||||||
|
if len(phy_addr) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buf: strings.Builder
|
||||||
|
|
||||||
|
for b, i in phy_addr {
|
||||||
|
if i > 0 {
|
||||||
|
strings.write_rune(&buf, ':')
|
||||||
|
}
|
||||||
|
|
||||||
|
hi := rune(MAC_HEX[b >> 4])
|
||||||
|
lo := rune(MAC_HEX[b & 15])
|
||||||
|
strings.write_rune(&buf, hi)
|
||||||
|
strings.write_rune(&buf, lo)
|
||||||
|
}
|
||||||
|
return strings.to_string(buf)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package net
|
||||||
|
//+build darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Implement. Can probably use the (current) Linux implementation,
|
||||||
|
// which will itself be switched over to talking to the kernel via NETLINK protocol
|
||||||
|
// once we have raw sockets.
|
||||||
|
|
||||||
|
unimplemented()
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package net
|
||||||
|
//+build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
|
||||||
|
This file uses `getifaddrs` libc call to enumerate interfaces.
|
||||||
|
TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
head: ^os.ifaddrs
|
||||||
|
|
||||||
|
if res := os._getifaddrs(&head); res < 0 {
|
||||||
|
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct.
|
||||||
|
We're going to have to iterate over a list and coalesce information as we go.
|
||||||
|
*/
|
||||||
|
ifaces: map[string]^Network_Interface
|
||||||
|
defer delete(ifaces)
|
||||||
|
|
||||||
|
for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next {
|
||||||
|
adapter_name := string(ifaddr.name)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if we have seen this interface name before so we can reuse the `Network_Interface`.
|
||||||
|
Else, create a new one.
|
||||||
|
*/
|
||||||
|
if adapter_name not_in ifaces {
|
||||||
|
ifaces[adapter_name] = new(Network_Interface)
|
||||||
|
ifaces[adapter_name].adapter_name = strings.clone(adapter_name)
|
||||||
|
}
|
||||||
|
iface := ifaces[adapter_name]
|
||||||
|
|
||||||
|
address: Address
|
||||||
|
netmask: Netmask
|
||||||
|
|
||||||
|
if ifaddr.address != nil {
|
||||||
|
switch int(ifaddr.address.sa_family) {
|
||||||
|
case os.AF_INET, os.AF_INET6:
|
||||||
|
address = _sockaddr_basic_to_endpoint(ifaddr.address).address
|
||||||
|
|
||||||
|
case os.AF_PACKET:
|
||||||
|
/*
|
||||||
|
For some obscure reason the 64-bit `getifaddrs` call returns a pointer to a
|
||||||
|
32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count
|
||||||
|
is truncated beyond usefulness.
|
||||||
|
|
||||||
|
We're not going to retrieve stats now. Instead this serves as a reminder to use
|
||||||
|
the NETLINK protocol for this purpose.
|
||||||
|
|
||||||
|
But in case you were curious:
|
||||||
|
stats := transmute(^os.rtnl_link_stats)ifaddr.data
|
||||||
|
fmt.println(stats)
|
||||||
|
*/
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifaddr.netmask != nil {
|
||||||
|
switch int(ifaddr.netmask.sa_family) {
|
||||||
|
case os.AF_INET, os.AF_INET6:
|
||||||
|
netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
|
||||||
|
switch int(ifaddr.broadcast_or_dest.sa_family) {
|
||||||
|
case os.AF_INET, os.AF_INET6:
|
||||||
|
broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
|
||||||
|
append(&iface.multicast, broadcast)
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if address != nil {
|
||||||
|
lease := Lease{
|
||||||
|
address = address,
|
||||||
|
netmask = netmask,
|
||||||
|
}
|
||||||
|
append(&iface.unicast, lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Refine this based on the type of adapter.
|
||||||
|
*/
|
||||||
|
state := Link_State{}
|
||||||
|
|
||||||
|
if .UP in ifaddr.flags {
|
||||||
|
state |= {.Up}
|
||||||
|
}
|
||||||
|
|
||||||
|
if .DORMANT in ifaddr.flags {
|
||||||
|
state |= {.Dormant}
|
||||||
|
}
|
||||||
|
|
||||||
|
if .LOOPBACK in ifaddr.flags {
|
||||||
|
state |= {.Loopback}
|
||||||
|
}
|
||||||
|
iface.link.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free the OS structures.
|
||||||
|
*/
|
||||||
|
os._freeifaddrs(head)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Turn the map into a slice to return.
|
||||||
|
*/
|
||||||
|
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||||
|
for _, iface in ifaces {
|
||||||
|
append(&_interfaces, iface^)
|
||||||
|
free(iface)
|
||||||
|
}
|
||||||
|
return _interfaces[:], {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package net
|
||||||
|
//+build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import sys "core:sys/windows"
|
||||||
|
import strings "core:strings"
|
||||||
|
|
||||||
|
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
buf: []u8
|
||||||
|
defer delete(buf)
|
||||||
|
|
||||||
|
buf_size: u32
|
||||||
|
res: u32
|
||||||
|
|
||||||
|
gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES {
|
||||||
|
res = sys.get_adapters_addresses(
|
||||||
|
.Unspecified, // Return both IPv4 and IPv6 adapters.
|
||||||
|
sys.GAA_Flags{
|
||||||
|
.Include_Prefix, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
|
||||||
|
.Include_Gateways, // (Vista+) Return the addresses of default gateways.
|
||||||
|
.Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
|
||||||
|
},
|
||||||
|
nil, // Reserved
|
||||||
|
(^sys.IP_Adapter_Addresses)(raw_data(buf)),
|
||||||
|
&buf_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
switch res {
|
||||||
|
case 111: // ERROR_BUFFER_OVERFLOW:
|
||||||
|
delete(buf)
|
||||||
|
buf = make([]u8, buf_size)
|
||||||
|
case 0:
|
||||||
|
break gaa
|
||||||
|
case:
|
||||||
|
return {}, Platform_Error(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if res != 0 {
|
||||||
|
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||||
|
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
|
||||||
|
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
|
||||||
|
if err1 != nil { return {}, Platform_Error(err1) }
|
||||||
|
|
||||||
|
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
|
||||||
|
if err2 != nil { return {}, Platform_Error(err2) }
|
||||||
|
|
||||||
|
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
|
||||||
|
if err3 != nil { return {}, Platform_Error(err3) }
|
||||||
|
|
||||||
|
interface := Network_Interface{
|
||||||
|
adapter_name = strings.clone(string(adapter.AdapterName)),
|
||||||
|
friendly_name = friendly_name,
|
||||||
|
description = description,
|
||||||
|
dns_suffix = dns_suffix,
|
||||||
|
|
||||||
|
mtu = adapter.MTU,
|
||||||
|
|
||||||
|
link = {
|
||||||
|
transmit_speed = adapter.TransmitLinkSpeed,
|
||||||
|
receive_speed = adapter.ReceiveLinkSpeed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) {
|
||||||
|
interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength])
|
||||||
|
}
|
||||||
|
|
||||||
|
for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next {
|
||||||
|
win_addr := parse_socket_address(u_addr.Address)
|
||||||
|
|
||||||
|
lease := Lease{
|
||||||
|
address = win_addr.address,
|
||||||
|
origin = {
|
||||||
|
prefix = Prefix_Origin(u_addr.PrefixOrigin),
|
||||||
|
suffix = Suffix_Origin(u_addr.SuffixOrigin),
|
||||||
|
},
|
||||||
|
lifetime = {
|
||||||
|
valid = u_addr.ValidLifetime,
|
||||||
|
preferred = u_addr.PreferredLifetime,
|
||||||
|
lease = u_addr.LeaseLifetime,
|
||||||
|
},
|
||||||
|
address_duplication = Address_Duplication(u_addr.DadState),
|
||||||
|
}
|
||||||
|
append(&interface.unicast, lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next {
|
||||||
|
addr := parse_socket_address(a_addr.Address)
|
||||||
|
append(&interface.anycast, addr.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next {
|
||||||
|
addr := parse_socket_address(m_addr.Address)
|
||||||
|
append(&interface.multicast, addr.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next {
|
||||||
|
addr := parse_socket_address(g_addr.Address)
|
||||||
|
append(&interface.gateways, addr.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address
|
||||||
|
interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address
|
||||||
|
|
||||||
|
switch adapter.OperStatus {
|
||||||
|
case .Up: interface.link.state = {.Up}
|
||||||
|
case .Down: interface.link.state = {.Down}
|
||||||
|
case .Testing: interface.link.state = {.Testing}
|
||||||
|
case .Dormant: interface.link.state = {.Dormant}
|
||||||
|
case .NotPresent: interface.link.state = {.Not_Present}
|
||||||
|
case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down}
|
||||||
|
case .Unknown: fallthrough
|
||||||
|
case: interface.link.state = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.tunnel_type = Tunnel_Type(adapter.TunnelType)
|
||||||
|
|
||||||
|
append(&_interfaces, interface)
|
||||||
|
}
|
||||||
|
|
||||||
|
return _interfaces[:], {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Interpret SOCKET_ADDRESS as an Address
|
||||||
|
*/
|
||||||
|
parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
|
||||||
|
if addr_in.lpSockaddr == nil {
|
||||||
|
return // Empty or invalid address type
|
||||||
|
}
|
||||||
|
|
||||||
|
sock := addr_in.lpSockaddr^
|
||||||
|
|
||||||
|
switch sock.sa_family {
|
||||||
|
case u16(sys.AF_INET):
|
||||||
|
win_addr := cast(^sys.sockaddr_in)addr_in.lpSockaddr
|
||||||
|
port := int(win_addr.sin_port)
|
||||||
|
return Endpoint {
|
||||||
|
address = IP4_Address(transmute([4]byte)win_addr.sin_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
|
||||||
|
case u16(sys.AF_INET6):
|
||||||
|
win_addr := cast(^sys.sockaddr_in6)addr_in.lpSockaddr
|
||||||
|
port := int(win_addr.sin6_port)
|
||||||
|
return Endpoint {
|
||||||
|
address = IP6_Address(transmute([8]u16be)win_addr.sin6_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case: return // Empty or invalid address type
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// +build windows, linux, darwin
|
||||||
|
package net
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022-2023 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022-2023 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022-2023 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
any_socket_to_socket :: proc(socket: Any_Socket) -> Socket {
|
||||||
|
switch s in socket {
|
||||||
|
case TCP_Socket: return Socket(s)
|
||||||
|
case UDP_Socket: return Socket(s)
|
||||||
|
case:
|
||||||
|
// TODO(tetra): Bluetooth, Raw
|
||||||
|
return Socket({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expects both hostname and port to be present in the `hostname_and_port` parameter, either as:
|
||||||
|
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
|
||||||
|
|
||||||
|
Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`.
|
||||||
|
*/
|
||||||
|
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
target := parse_hostname_or_endpoint(hostname_and_port) or_return
|
||||||
|
switch t in target {
|
||||||
|
case Endpoint:
|
||||||
|
return dial_tcp_from_endpoint(t, options)
|
||||||
|
case Host:
|
||||||
|
if t.port == 0 {
|
||||||
|
return 0, .Port_Required
|
||||||
|
}
|
||||||
|
ep4, ep6 := resolve(t.hostname) or_return
|
||||||
|
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||||
|
ep.port = t.port
|
||||||
|
return dial_tcp_from_endpoint(ep, options)
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expects the `hostname` as a string and `port` as a `int`.
|
||||||
|
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
|
||||||
|
|
||||||
|
If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
|
||||||
|
*/
|
||||||
|
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
target := parse_hostname_or_endpoint(hostname) or_return
|
||||||
|
switch t in target {
|
||||||
|
case Endpoint:
|
||||||
|
return dial_tcp_from_endpoint({t.address, port}, options)
|
||||||
|
case Host:
|
||||||
|
if port == 0 {
|
||||||
|
return 0, .Port_Required
|
||||||
|
}
|
||||||
|
ep4, ep6 := resolve(t.hostname) or_return
|
||||||
|
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||||
|
ep.port = port
|
||||||
|
return dial_tcp_from_endpoint(ep, options)
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial from an Address
|
||||||
|
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
return dial_tcp_from_endpoint({address, port}, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
return _dial_tcp_from_endpoint(endpoint, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
dial_tcp :: proc{
|
||||||
|
dial_tcp_from_endpoint,
|
||||||
|
dial_tcp_from_address_and_port,
|
||||||
|
dial_tcp_from_hostname_and_port_string,
|
||||||
|
dial_tcp_from_hostname_with_port_override,
|
||||||
|
}
|
||||||
|
|
||||||
|
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||||
|
return _create_socket(family, protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||||
|
return _bind(socket, ep)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This type of socket becomes bound when you try to send data.
|
||||||
|
It is likely what you want if you want to send data unsolicited.
|
||||||
|
|
||||||
|
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
|
||||||
|
*/
|
||||||
|
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
|
||||||
|
sock := create_socket(family, .UDP) or_return
|
||||||
|
socket = sock.(UDP_Socket)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This type of socket is bound immediately, which enables it to receive data on the port.
|
||||||
|
Since it's UDP, it's also able to send data without receiving any first.
|
||||||
|
|
||||||
|
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
|
||||||
|
The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
|
||||||
|
*/
|
||||||
|
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
|
||||||
|
if bound_address == nil {
|
||||||
|
return {}, .Bad_Address
|
||||||
|
}
|
||||||
|
socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
|
||||||
|
bind(socket, {bound_address, port}) or_return
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
assert(backlog > 0 && backlog < int(max(i32)))
|
||||||
|
|
||||||
|
return _listen_tcp(interface_endpoint, backlog)
|
||||||
|
}
|
||||||
|
|
||||||
|
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||||
|
return _accept_tcp(socket, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
close :: proc(socket: Any_Socket) {
|
||||||
|
_close(socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||||
|
return _recv_tcp(socket, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||||
|
return _recv_udp(socket, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
recv :: proc{recv_tcp, recv_udp}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Repeatedly sends data until the entire buffer is sent.
|
||||||
|
If a send fails before all data is sent, returns the amount sent up to that point.
|
||||||
|
*/
|
||||||
|
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
return _send_tcp(socket, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sends a single UDP datagram packet.
|
||||||
|
|
||||||
|
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
|
||||||
|
UDP packets are not guarenteed to be received in order.
|
||||||
|
*/
|
||||||
|
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
return _send_udp(socket, buf, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
send :: proc{send_tcp, send_udp}
|
||||||
|
|
||||||
|
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||||
|
return _shutdown(socket, manner)
|
||||||
|
}
|
||||||
|
|
||||||
|
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||||
|
return _set_option(socket, option, value, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||||
|
return _set_blocking(socket, should_block)
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
package net
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:os"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
Socket_Option :: enum c.int {
|
||||||
|
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||||
|
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||||
|
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||||
|
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||||
|
Linger = c.int(os.SO_LINGER),
|
||||||
|
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||||
|
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||||
|
Receive_Timeout = c.int(os.SO_RCVTIMEO),
|
||||||
|
Send_Timeout = c.int(os.SO_SNDTIMEO),
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||||
|
c_type, c_protocol, c_family: int
|
||||||
|
|
||||||
|
switch family {
|
||||||
|
case .IP4: c_family = os.AF_INET
|
||||||
|
case .IP6: c_family = os.AF_INET6
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||||
|
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = Create_Socket_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: return TCP_Socket(sock), nil
|
||||||
|
case .UDP: return UDP_Socket(sock), nil
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||||
|
if endpoint.port == 0 {
|
||||||
|
return 0, .Port_Required
|
||||||
|
}
|
||||||
|
|
||||||
|
family := family_from_endpoint(endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
skt = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||||
|
// bypass the cooldown period, and allow the next run of the program to
|
||||||
|
// use the same address immediately.
|
||||||
|
_ = set_option(skt, .Reuse_Address, true)
|
||||||
|
|
||||||
|
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||||
|
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Dial_Error(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||||
|
sockaddr := _endpoint_to_sockaddr(ep)
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Bind_Error(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||||
|
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||||
|
|
||||||
|
family := family_from_endpoint(interface_endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
skt = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||||
|
// bypass the cooldown period, and allow the next run of the program to
|
||||||
|
// use the same address immediately.
|
||||||
|
//
|
||||||
|
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||||
|
set_option(sock, .Reuse_Address, true) or_return
|
||||||
|
|
||||||
|
bind(sock, interface_endpoint) or_return
|
||||||
|
|
||||||
|
res := os.listen(os.Socket(skt), backlog)
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Listen_Error(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||||
|
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||||
|
sockaddrlen := c.int(size_of(sockaddr))
|
||||||
|
|
||||||
|
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = Accept_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client = TCP_Socket(client_sock)
|
||||||
|
source = _sockaddr_to_endpoint(&sockaddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_close :: proc(skt: Any_Socket) {
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
os.close(os.Handle(os.Socket(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = TCP_Recv_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from: os.SOCKADDR_STORAGE_LH
|
||||||
|
fromsize := c.int(size_of(from))
|
||||||
|
res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = UDP_Recv_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read = int(res)
|
||||||
|
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
for bytes_written < len(buf) {
|
||||||
|
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||||
|
remaining := buf[bytes_written:][:limit]
|
||||||
|
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = TCP_Send_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written += int(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
toaddr := _endpoint_to_sockaddr(to)
|
||||||
|
for bytes_written < len(buf) {
|
||||||
|
limit := min(1<<31, len(buf) - bytes_written)
|
||||||
|
remaining := buf[bytes_written:][:limit]
|
||||||
|
res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = UDP_Send_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written += int(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
res := os.shutdown(os.Socket(s), int(manner))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
return Shutdown_Error(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||||
|
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||||
|
|
||||||
|
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||||
|
// it _has_ to be a b32.
|
||||||
|
// I haven't tested if you can give more than that.
|
||||||
|
bool_value: b32
|
||||||
|
int_value: i32
|
||||||
|
timeval_value: os.Timeval
|
||||||
|
|
||||||
|
ptr: rawptr
|
||||||
|
len: os.socklen_t
|
||||||
|
|
||||||
|
switch option {
|
||||||
|
case
|
||||||
|
.Reuse_Address,
|
||||||
|
.Keep_Alive,
|
||||||
|
.Out_Of_Bounds_Data_Inline,
|
||||||
|
.TCP_Nodelay:
|
||||||
|
// TODO: verify whether these are options or not on Linux
|
||||||
|
// .Broadcast,
|
||||||
|
// .Conditional_Accept,
|
||||||
|
// .Dont_Linger:
|
||||||
|
switch x in value {
|
||||||
|
case bool, b8:
|
||||||
|
x2 := x
|
||||||
|
bool_value = b32((^bool)(&x2)^)
|
||||||
|
case b16:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b32:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b64:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be a boolean here", loc)
|
||||||
|
}
|
||||||
|
ptr = &bool_value
|
||||||
|
len = size_of(bool_value)
|
||||||
|
case
|
||||||
|
.Linger,
|
||||||
|
.Send_Timeout,
|
||||||
|
.Receive_Timeout:
|
||||||
|
t, ok := value.(time.Duration)
|
||||||
|
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||||
|
|
||||||
|
nanos := time.duration_nanoseconds(t)
|
||||||
|
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||||
|
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||||
|
|
||||||
|
ptr = &timeval_value
|
||||||
|
len = size_of(timeval_value)
|
||||||
|
case
|
||||||
|
.Receive_Buffer_Size,
|
||||||
|
.Send_Buffer_Size:
|
||||||
|
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||||
|
switch i in value {
|
||||||
|
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||||
|
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||||
|
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||||
|
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||||
|
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||||
|
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be an integer here", loc)
|
||||||
|
}
|
||||||
|
ptr = &int_value
|
||||||
|
len = size_of(int_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
skt := any_socket_to_socket(s)
|
||||||
|
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
return Socket_Option_Error(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||||
|
// TODO: Implement
|
||||||
|
unimplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||||
|
switch a in ep.address {
|
||||||
|
case IP4_Address:
|
||||||
|
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||||
|
sin_port = u16be(ep.port),
|
||||||
|
sin_addr = transmute(os.in_addr) a,
|
||||||
|
sin_family = u8(os.AF_INET),
|
||||||
|
sin_len = size_of(os.sockaddr_in),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case IP6_Address:
|
||||||
|
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||||
|
sin6_port = u16be(ep.port),
|
||||||
|
sin6_addr = transmute(os.in6_addr) a,
|
||||||
|
sin6_family = u8(os.AF_INET6),
|
||||||
|
sin6_len = size_of(os.sockaddr_in6),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
@private
|
||||||
|
_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||||
|
switch native_addr.family {
|
||||||
|
case u8(os.AF_INET):
|
||||||
|
addr := cast(^os.sockaddr_in) native_addr
|
||||||
|
port := int(addr.sin_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case u8(os.AF_INET6):
|
||||||
|
addr := cast(^os.sockaddr_in6) native_addr
|
||||||
|
port := int(addr.sin6_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
panic("native_addr is neither IP4 or IP6 address")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
package net
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:os"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
Socket_Option :: enum c.int {
|
||||||
|
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||||
|
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||||
|
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||||
|
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||||
|
Linger = c.int(os.SO_LINGER),
|
||||||
|
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||||
|
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||||
|
Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW),
|
||||||
|
Send_Timeout = c.int(os.SO_SNDTIMEO_NEW),
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||||
|
c_type, c_protocol, c_family: int
|
||||||
|
|
||||||
|
switch family {
|
||||||
|
case .IP4: c_family = os.AF_INET
|
||||||
|
case .IP6: c_family = os.AF_INET6
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||||
|
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = Create_Socket_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: return TCP_Socket(sock), nil
|
||||||
|
case .UDP: return UDP_Socket(sock), nil
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||||
|
if endpoint.port == 0 {
|
||||||
|
return 0, .Port_Required
|
||||||
|
}
|
||||||
|
|
||||||
|
family := family_from_endpoint(endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
skt = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||||
|
// bypass the cooldown period, and allow the next run of the program to
|
||||||
|
// use the same address immediately.
|
||||||
|
_ = set_option(skt, .Reuse_Address, true)
|
||||||
|
|
||||||
|
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||||
|
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Dial_Error(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.no_delay {
|
||||||
|
_ = _set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||||
|
sockaddr := _endpoint_to_sockaddr(ep)
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Bind_Error(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||||
|
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||||
|
|
||||||
|
family := family_from_endpoint(interface_endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
skt = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||||
|
// bypass the cooldown period, and allow the next run of the program to
|
||||||
|
// use the same address immediately.
|
||||||
|
//
|
||||||
|
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||||
|
set_option(sock, .Reuse_Address, true) or_return
|
||||||
|
|
||||||
|
bind(sock, interface_endpoint) or_return
|
||||||
|
|
||||||
|
res := os.listen(os.Socket(skt), backlog)
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
err = Listen_Error(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||||
|
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||||
|
sockaddrlen := c.int(size_of(sockaddr))
|
||||||
|
|
||||||
|
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = Accept_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client = TCP_Socket(client_sock)
|
||||||
|
source = _sockaddr_storage_to_endpoint(&sockaddr)
|
||||||
|
if options.no_delay {
|
||||||
|
_ = _set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_close :: proc(skt: Any_Socket) {
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
os.close(os.Handle(os.Socket(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = TCP_Recv_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from: os.SOCKADDR_STORAGE_LH = ---
|
||||||
|
fromsize := c.int(size_of(from))
|
||||||
|
|
||||||
|
// NOTE(tetra): On Linux, if the buffer is too small to fit the entire datagram payload, the rest is silently discarded,
|
||||||
|
// and no error is returned.
|
||||||
|
// However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read.
|
||||||
|
// We can use this fact to detect this condition and return .Buffer_Too_Small.
|
||||||
|
res, ok := os.recvfrom(os.Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = UDP_Recv_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read = int(res)
|
||||||
|
remote_endpoint = _sockaddr_storage_to_endpoint(&from)
|
||||||
|
|
||||||
|
if bytes_read > len(buf) {
|
||||||
|
// NOTE(tetra): The buffer has been filled, with a partial message.
|
||||||
|
bytes_read = len(buf)
|
||||||
|
err = .Buffer_Too_Small
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
for bytes_written < len(buf) {
|
||||||
|
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||||
|
remaining := buf[bytes_written:][:limit]
|
||||||
|
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||||
|
if ok != os.ERROR_NONE {
|
||||||
|
err = TCP_Send_Error(ok)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written += int(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
toaddr := _endpoint_to_sockaddr(to)
|
||||||
|
res, os_err := os.sendto(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr))
|
||||||
|
if os_err != os.ERROR_NONE {
|
||||||
|
err = UDP_Send_Error(os_err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written = int(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||||
|
s := any_socket_to_socket(skt)
|
||||||
|
res := os.shutdown(os.Socket(s), int(manner))
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
return Shutdown_Error(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||||
|
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||||
|
|
||||||
|
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||||
|
// it _has_ to be a b32.
|
||||||
|
// I haven't tested if you can give more than that.
|
||||||
|
bool_value: b32
|
||||||
|
int_value: i32
|
||||||
|
timeval_value: os.Timeval
|
||||||
|
|
||||||
|
ptr: rawptr
|
||||||
|
len: os.socklen_t
|
||||||
|
|
||||||
|
switch option {
|
||||||
|
case
|
||||||
|
.Reuse_Address,
|
||||||
|
.Keep_Alive,
|
||||||
|
.Out_Of_Bounds_Data_Inline,
|
||||||
|
.TCP_Nodelay:
|
||||||
|
// TODO: verify whether these are options or not on Linux
|
||||||
|
// .Broadcast,
|
||||||
|
// .Conditional_Accept,
|
||||||
|
// .Dont_Linger:
|
||||||
|
switch x in value {
|
||||||
|
case bool, b8:
|
||||||
|
x2 := x
|
||||||
|
bool_value = b32((^bool)(&x2)^)
|
||||||
|
case b16:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b32:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b64:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be a boolean here", loc)
|
||||||
|
}
|
||||||
|
ptr = &bool_value
|
||||||
|
len = size_of(bool_value)
|
||||||
|
case
|
||||||
|
.Linger,
|
||||||
|
.Send_Timeout,
|
||||||
|
.Receive_Timeout:
|
||||||
|
t, ok := value.(time.Duration)
|
||||||
|
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||||
|
|
||||||
|
nanos := time.duration_nanoseconds(t)
|
||||||
|
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||||
|
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||||
|
|
||||||
|
ptr = &timeval_value
|
||||||
|
len = size_of(timeval_value)
|
||||||
|
case
|
||||||
|
.Receive_Buffer_Size,
|
||||||
|
.Send_Buffer_Size:
|
||||||
|
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||||
|
switch i in value {
|
||||||
|
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||||
|
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||||
|
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||||
|
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||||
|
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||||
|
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be an integer here", loc)
|
||||||
|
}
|
||||||
|
ptr = &int_value
|
||||||
|
len = size_of(int_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
skt := any_socket_to_socket(s)
|
||||||
|
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||||
|
if res != os.ERROR_NONE {
|
||||||
|
return Socket_Option_Error(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||||
|
socket := any_socket_to_socket(socket)
|
||||||
|
|
||||||
|
flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
|
||||||
|
if getfl_err != os.ERROR_NONE {
|
||||||
|
return Set_Blocking_Error(getfl_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_block {
|
||||||
|
flags &= ~int(os.O_NONBLOCK)
|
||||||
|
} else {
|
||||||
|
flags |= int(os.O_NONBLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
|
||||||
|
if setfl_err != os.ERROR_NONE {
|
||||||
|
return Set_Blocking_Error(setfl_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||||
|
switch a in ep.address {
|
||||||
|
case IP4_Address:
|
||||||
|
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||||
|
sin_port = u16be(ep.port),
|
||||||
|
sin_addr = transmute(os.in_addr) a,
|
||||||
|
sin_family = u16(os.AF_INET),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case IP6_Address:
|
||||||
|
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||||
|
sin6_port = u16be(ep.port),
|
||||||
|
sin6_addr = transmute(os.in6_addr) a,
|
||||||
|
sin6_family = u16(os.AF_INET6),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||||
|
switch native_addr.ss_family {
|
||||||
|
case u16(os.AF_INET):
|
||||||
|
addr := cast(^os.sockaddr_in) native_addr
|
||||||
|
port := int(addr.sin_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case u16(os.AF_INET6):
|
||||||
|
addr := cast(^os.sockaddr_in6) native_addr
|
||||||
|
port := int(addr.sin6_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
panic("native_addr is neither IP4 or IP6 address")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
|
||||||
|
switch native_addr.sa_family {
|
||||||
|
case u16(os.AF_INET):
|
||||||
|
addr := cast(^os.sockaddr_in) native_addr
|
||||||
|
port := int(addr.sin_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case u16(os.AF_INET6):
|
||||||
|
addr := cast(^os.sockaddr_in6) native_addr
|
||||||
|
port := int(addr.sin6_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
panic("native_addr is neither IP4 or IP6 address")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
package net
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import win "core:sys/windows"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
@(init, private)
|
||||||
|
ensure_winsock_initialized :: proc() {
|
||||||
|
win.ensure_winsock_initialized()
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||||
|
c_type, c_protocol, c_family: c.int
|
||||||
|
|
||||||
|
switch family {
|
||||||
|
case .IP4: c_family = win.AF_INET
|
||||||
|
case .IP6: c_family = win.AF_INET6
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: c_type = win.SOCK_STREAM; c_protocol = win.IPPROTO_TCP
|
||||||
|
case .UDP: c_type = win.SOCK_DGRAM; c_protocol = win.IPPROTO_UDP
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
sock := win.socket(c_family, c_type, c_protocol)
|
||||||
|
if sock == win.INVALID_SOCKET {
|
||||||
|
err = Create_Socket_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case .TCP: return TCP_Socket(sock), nil
|
||||||
|
case .UDP: return UDP_Socket(sock), nil
|
||||||
|
case:
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
if endpoint.port == 0 {
|
||||||
|
err = .Port_Required
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
family := family_from_endpoint(endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
socket = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||||
|
// bypass the cooldown period, and allow the next run of the program to
|
||||||
|
// use the same address immediately.
|
||||||
|
_ = set_option(socket, .Reuse_Address, true)
|
||||||
|
|
||||||
|
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||||
|
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
|
||||||
|
if res < 0 {
|
||||||
|
err = Dial_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.no_delay {
|
||||||
|
_ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||||
|
sockaddr := _endpoint_to_sockaddr(ep)
|
||||||
|
sock := any_socket_to_socket(socket)
|
||||||
|
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
|
||||||
|
if res < 0 {
|
||||||
|
err = Bind_Error(win.WSAGetLastError())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||||
|
family := family_from_endpoint(interface_endpoint)
|
||||||
|
sock := create_socket(family, .TCP) or_return
|
||||||
|
socket = sock.(TCP_Socket)
|
||||||
|
|
||||||
|
// NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will
|
||||||
|
// prevent hijacking of the server's endpoint by other applications.
|
||||||
|
set_option(socket, .Exclusive_Addr_Use, true) or_return
|
||||||
|
|
||||||
|
bind(sock, interface_endpoint) or_return
|
||||||
|
|
||||||
|
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
|
||||||
|
err = Listen_Error(win.WSAGetLastError())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||||
|
for {
|
||||||
|
sockaddr: win.SOCKADDR_STORAGE_LH
|
||||||
|
sockaddrlen := c.int(size_of(sockaddr))
|
||||||
|
client_sock := win.accept(win.SOCKET(sock), &sockaddr, &sockaddrlen)
|
||||||
|
if int(client_sock) == win.SOCKET_ERROR {
|
||||||
|
e := win.WSAGetLastError()
|
||||||
|
if e == win.WSAECONNRESET {
|
||||||
|
// NOTE(tetra): Reset just means that a client that connection immediately lost the connection.
|
||||||
|
// There's no need to concern the user with this, so we handle it for them.
|
||||||
|
// On Linux, this error isn't possible in the first place according the man pages, so we also
|
||||||
|
// can do this to match the behaviour.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = Accept_Error(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client = TCP_Socket(client_sock)
|
||||||
|
source = _sockaddr_to_endpoint(&sockaddr)
|
||||||
|
if options.no_delay {
|
||||||
|
_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_close :: proc(socket: Any_Socket) {
|
||||||
|
if s := any_socket_to_socket(socket); s != {} {
|
||||||
|
win.closesocket(win.SOCKET(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
|
||||||
|
if res < 0 {
|
||||||
|
err = TCP_Recv_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||||
|
if len(buf) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
from: win.SOCKADDR_STORAGE_LH
|
||||||
|
fromsize := c.int(size_of(from))
|
||||||
|
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
|
||||||
|
if res < 0 {
|
||||||
|
err = UDP_Recv_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read = int(res)
|
||||||
|
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
for bytes_written < len(buf) {
|
||||||
|
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||||
|
remaining := buf[bytes_written:]
|
||||||
|
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
|
||||||
|
if res < 0 {
|
||||||
|
err = TCP_Send_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written += int(res)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||||
|
if len(buf) > int(max(c.int)) {
|
||||||
|
// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
|
||||||
|
err = .Message_Too_Long
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toaddr := _endpoint_to_sockaddr(to)
|
||||||
|
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
|
||||||
|
if res < 0 {
|
||||||
|
err = UDP_Send_Error(win.WSAGetLastError())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes_written = int(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||||
|
s := any_socket_to_socket(socket)
|
||||||
|
res := win.shutdown(win.SOCKET(s), c.int(manner))
|
||||||
|
if res < 0 {
|
||||||
|
return Shutdown_Error(win.WSAGetLastError())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||||
|
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
|
||||||
|
|
||||||
|
bool_value: b32
|
||||||
|
int_value: i32
|
||||||
|
linger_value: win.LINGER
|
||||||
|
|
||||||
|
ptr: rawptr
|
||||||
|
len: c.int
|
||||||
|
|
||||||
|
switch option {
|
||||||
|
case
|
||||||
|
.Reuse_Address,
|
||||||
|
.Exclusive_Addr_Use,
|
||||||
|
.Keep_Alive,
|
||||||
|
.Out_Of_Bounds_Data_Inline,
|
||||||
|
.TCP_Nodelay,
|
||||||
|
.Broadcast,
|
||||||
|
.Conditional_Accept,
|
||||||
|
.Dont_Linger:
|
||||||
|
switch x in value {
|
||||||
|
case bool, b8:
|
||||||
|
x2 := x
|
||||||
|
bool_value = b32((^bool)(&x2)^)
|
||||||
|
case b16:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b32:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case b64:
|
||||||
|
bool_value = b32(x)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be a boolean here", loc)
|
||||||
|
}
|
||||||
|
ptr = &bool_value
|
||||||
|
len = size_of(bool_value)
|
||||||
|
case .Linger:
|
||||||
|
t, ok := value.(time.Duration)
|
||||||
|
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||||
|
|
||||||
|
num_secs := i64(time.duration_seconds(t))
|
||||||
|
if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds
|
||||||
|
if num_secs > i64(max(u16)) do return .Value_Out_Of_Range
|
||||||
|
linger_value.l_onoff = 1
|
||||||
|
linger_value.l_linger = c.ushort(num_secs)
|
||||||
|
|
||||||
|
ptr = &linger_value
|
||||||
|
len = size_of(linger_value)
|
||||||
|
case
|
||||||
|
.Receive_Timeout,
|
||||||
|
.Send_Timeout:
|
||||||
|
t, ok := value.(time.Duration)
|
||||||
|
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||||
|
|
||||||
|
int_value = i32(time.duration_milliseconds(t))
|
||||||
|
ptr = &int_value
|
||||||
|
len = size_of(int_value)
|
||||||
|
|
||||||
|
case
|
||||||
|
.Receive_Buffer_Size,
|
||||||
|
.Send_Buffer_Size:
|
||||||
|
switch i in value {
|
||||||
|
case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^)
|
||||||
|
case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^)
|
||||||
|
case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^)
|
||||||
|
case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^)
|
||||||
|
case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^)
|
||||||
|
case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^)
|
||||||
|
case:
|
||||||
|
panic("set_option() value must be an integer here", loc)
|
||||||
|
}
|
||||||
|
ptr = &int_value
|
||||||
|
len = size_of(int_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket := any_socket_to_socket(s)
|
||||||
|
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
|
||||||
|
if res < 0 {
|
||||||
|
return Socket_Option_Error(win.WSAGetLastError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||||
|
socket := any_socket_to_socket(socket)
|
||||||
|
arg: win.DWORD = 0 if should_block else 1
|
||||||
|
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
|
||||||
|
if res == win.SOCKET_ERROR {
|
||||||
|
return Set_Blocking_Error(win.WSAGetLastError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
|
||||||
|
switch a in ep.address {
|
||||||
|
case IP4_Address:
|
||||||
|
(^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in {
|
||||||
|
sin_port = u16be(win.USHORT(ep.port)),
|
||||||
|
sin_addr = transmute(win.in_addr) a,
|
||||||
|
sin_family = u16(win.AF_INET),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case IP6_Address:
|
||||||
|
(^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 {
|
||||||
|
sin6_port = u16be(win.USHORT(ep.port)),
|
||||||
|
sin6_addr = transmute(win.in6_addr) a,
|
||||||
|
sin6_family = u16(win.AF_INET6),
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||||
|
switch native_addr.ss_family {
|
||||||
|
case u16(win.AF_INET):
|
||||||
|
addr := cast(^win.sockaddr_in) native_addr
|
||||||
|
port := int(addr.sin_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case u16(win.AF_INET6):
|
||||||
|
addr := cast(^win.sockaddr_in6) native_addr
|
||||||
|
port := int(addr.sin6_port)
|
||||||
|
ep = Endpoint {
|
||||||
|
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
panic("native_addr is neither IP4 or IP6 address")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
package net
|
||||||
|
/*
|
||||||
|
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||||
|
For other protocols and their features, see subdirectories of this package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||||
|
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||||
|
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
|
List of contributors:
|
||||||
|
Tetralux: Initial implementation
|
||||||
|
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||||
|
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:strconv"
|
||||||
|
import "core:unicode/utf8"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
|
||||||
|
s := url
|
||||||
|
|
||||||
|
i := strings.last_index(s, "://")
|
||||||
|
if i >= 0 {
|
||||||
|
scheme = s[:i]
|
||||||
|
s = s[i+3:]
|
||||||
|
}
|
||||||
|
|
||||||
|
i = strings.index(s, "?")
|
||||||
|
if i != -1 {
|
||||||
|
query_str := s[i+1:]
|
||||||
|
s = s[:i]
|
||||||
|
if query_str != "" {
|
||||||
|
queries_parts := strings.split(query_str, "&")
|
||||||
|
queries = make(map[string]string, len(queries_parts), allocator)
|
||||||
|
for q in queries_parts {
|
||||||
|
parts := strings.split(q, "=")
|
||||||
|
switch len(parts) {
|
||||||
|
case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
|
||||||
|
case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
|
||||||
|
case: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = strings.index(s, "/")
|
||||||
|
if i == -1 {
|
||||||
|
host = s
|
||||||
|
path = "/"
|
||||||
|
} else {
|
||||||
|
host = s[:i]
|
||||||
|
path = s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
|
||||||
|
using strings
|
||||||
|
|
||||||
|
b := builder_make(allocator)
|
||||||
|
builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
|
||||||
|
|
||||||
|
write_string(&b, scheme)
|
||||||
|
write_string(&b, "://")
|
||||||
|
write_string(&b, trim_space(host))
|
||||||
|
|
||||||
|
if path != "" {
|
||||||
|
if path[0] != '/' do write_string(&b, "/")
|
||||||
|
write_string(&b, trim_space(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if len(queries) > 0 do write_string(&b, "?")
|
||||||
|
for query_name, query_value in queries {
|
||||||
|
write_string(&b, query_name)
|
||||||
|
if query_value != "" {
|
||||||
|
write_string(&b, "=")
|
||||||
|
write_string(&b, query_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to_string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
percent_encode :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
|
using strings
|
||||||
|
|
||||||
|
b := builder_make(allocator)
|
||||||
|
builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
|
||||||
|
|
||||||
|
for ch in s {
|
||||||
|
switch ch {
|
||||||
|
case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
|
||||||
|
write_rune(&b, ch)
|
||||||
|
case:
|
||||||
|
bytes, n := utf8.encode_rune(ch)
|
||||||
|
for byte in bytes[:n] {
|
||||||
|
buf: [2]u8 = ---
|
||||||
|
t := strconv.append_int(buf[:], i64(byte), 16)
|
||||||
|
write_rune(&b, '%')
|
||||||
|
write_string(&b, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to_string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
|
||||||
|
using strings
|
||||||
|
|
||||||
|
b := builder_make(allocator)
|
||||||
|
builder_grow(&b, len(encoded_string))
|
||||||
|
defer if !ok do builder_destroy(&b)
|
||||||
|
|
||||||
|
stack_buf: [4]u8
|
||||||
|
pending := mem.buffer_from_slice(stack_buf[:])
|
||||||
|
s := encoded_string
|
||||||
|
|
||||||
|
for len(s) > 0 {
|
||||||
|
i := index_rune(s, '%')
|
||||||
|
if i == -1 {
|
||||||
|
write_string(&b, s) // no '%'s; the string is already decoded
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
write_string(&b, s[:i])
|
||||||
|
s = s[i:]
|
||||||
|
|
||||||
|
if len(s) == 0 do return // percent without anything after it
|
||||||
|
s = s[1:]
|
||||||
|
|
||||||
|
if s[0] == '%' {
|
||||||
|
write_rune(&b, '%')
|
||||||
|
s = s[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s) < 2 do return // percent without encoded value
|
||||||
|
|
||||||
|
n: int
|
||||||
|
n, _ = strconv.parse_int(s[:2], 16)
|
||||||
|
switch n {
|
||||||
|
case 0x20: write_rune(&b, ' ')
|
||||||
|
case 0x21: write_rune(&b, '!')
|
||||||
|
case 0x23: write_rune(&b, '#')
|
||||||
|
case 0x24: write_rune(&b, '$')
|
||||||
|
case 0x25: write_rune(&b, '%')
|
||||||
|
case 0x26: write_rune(&b, '&')
|
||||||
|
case 0x27: write_rune(&b, '\'')
|
||||||
|
case 0x28: write_rune(&b, '(')
|
||||||
|
case 0x29: write_rune(&b, ')')
|
||||||
|
case 0x2A: write_rune(&b, '*')
|
||||||
|
case 0x2B: write_rune(&b, '+')
|
||||||
|
case 0x2C: write_rune(&b, ',')
|
||||||
|
case 0x2F: write_rune(&b, '/')
|
||||||
|
case 0x3A: write_rune(&b, ':')
|
||||||
|
case 0x3B: write_rune(&b, ';')
|
||||||
|
case 0x3D: write_rune(&b, '=')
|
||||||
|
case 0x3F: write_rune(&b, '?')
|
||||||
|
case 0x40: write_rune(&b, '@')
|
||||||
|
case 0x5B: write_rune(&b, '[')
|
||||||
|
case 0x5D: write_rune(&b, ']')
|
||||||
|
case:
|
||||||
|
// utf-8 bytes
|
||||||
|
// TODO(tetra): Audit this - 4 bytes???
|
||||||
|
append(&pending, s[0])
|
||||||
|
append(&pending, s[1])
|
||||||
|
if len(pending) == 4 {
|
||||||
|
r, _ := utf8.decode_rune(pending[:])
|
||||||
|
write_rune(&b, r)
|
||||||
|
clear(&pending)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
decoded_string = to_string(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO: encoding/base64 is broken...
|
||||||
|
//
|
||||||
|
|
||||||
|
// // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
|
||||||
|
// // make a table for this ... sigh - so this'll do for now.
|
||||||
|
/*
|
||||||
|
base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
|
||||||
|
out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
|
||||||
|
for b, i in out {
|
||||||
|
switch b {
|
||||||
|
case '+': out[i] = '-';
|
||||||
|
case '/': out[i] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i := len(out)-1;
|
||||||
|
for ; i >= 0; i -= 1 {
|
||||||
|
if out[i] != '=' do break;
|
||||||
|
}
|
||||||
|
return string(out[:i+1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
|
||||||
|
size := len(s);
|
||||||
|
padding := 0;
|
||||||
|
for size % 4 != 0 {
|
||||||
|
size += 1; // TODO: SPEED
|
||||||
|
padding += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp := make([]byte, size, context.temp_allocator);
|
||||||
|
copy(temp, transmute([]byte) s);
|
||||||
|
|
||||||
|
for b, i in temp {
|
||||||
|
switch b {
|
||||||
|
case '-': temp[i] = '+';
|
||||||
|
case '_': temp[i] = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for in 0..padding-1 {
|
||||||
|
temp[len(temp)-1] = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.decode(string(temp), base64.DEC_TABLE, allocator);
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -1425,7 +1425,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
|||||||
return es
|
return es
|
||||||
|
|
||||||
case "force_inline", "force_no_inline":
|
case "force_inline", "force_no_inline":
|
||||||
expr := parse_inlining_operand(p, true, tok)
|
expr := parse_inlining_operand(p, true, tag)
|
||||||
es := ast.new(ast.Expr_Stmt, expr.pos, expr.end)
|
es := ast.new(ast.Expr_Stmt, expr.pos, expr.end)
|
||||||
es.expr = expr
|
es.expr = expr
|
||||||
return es
|
return es
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fullpath := make([]byte, len(dirpath)+1+len(filename))
|
fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator)
|
||||||
copy(fullpath, dirpath)
|
copy(fullpath, dirpath)
|
||||||
copy(fullpath[len(dirpath):], "/")
|
copy(fullpath[len(dirpath):], "/")
|
||||||
copy(fullpath[len(dirpath)+1:], filename)
|
copy(fullpath[len(dirpath)+1:], filename)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package os
|
|||||||
|
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||||
dirp: Dir
|
dirp: Dir
|
||||||
@@ -51,6 +52,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
|
fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
|
||||||
defer delete(fullpath, context.temp_allocator)
|
defer delete(fullpath, context.temp_allocator)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package os
|
|||||||
|
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||||
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
|
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
|
||||||
@@ -65,13 +66,16 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
|||||||
n = -1
|
n = -1
|
||||||
size = 100
|
size = 100
|
||||||
}
|
}
|
||||||
dfi := make([dynamic]File_Info, 0, size)
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
|
|
||||||
wpath: []u16
|
wpath: []u16
|
||||||
wpath, err = cleanpath_from_handle_u16(fd)
|
wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
|
||||||
if len(wpath) == 0 || err != ERROR_NONE {
|
if len(wpath) == 0 || err != ERROR_NONE {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dfi := make([dynamic]File_Info, 0, size)
|
||||||
|
|
||||||
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
|
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
|
||||||
copy(wpath_search, wpath)
|
copy(wpath_search, wpath)
|
||||||
wpath_search[len(wpath)+0] = '\\'
|
wpath_search[len(wpath)+0] = '\\'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package os
|
package os
|
||||||
|
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
// lookup_env gets the value of the environment variable named by the key
|
// 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
|
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
||||||
@@ -18,6 +19,8 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
|
|
||||||
b := make([dynamic]u16, n, context.temp_allocator)
|
b := make([dynamic]u16, n, context.temp_allocator)
|
||||||
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
|
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
@@ -87,6 +90,7 @@ environ :: proc(allocator := context.allocator) -> []string {
|
|||||||
|
|
||||||
// clear_env deletes all environment variables
|
// clear_env deletes all environment variables
|
||||||
clear_env :: proc() {
|
clear_env :: proc() {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
envs := environ(context.temp_allocator)
|
envs := environ(context.temp_allocator)
|
||||||
for env in envs {
|
for env in envs {
|
||||||
for j in 1..<len(env) {
|
for j in 1..<len(env) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package os
|
|||||||
|
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
import "core:intrinsics"
|
import "core:intrinsics"
|
||||||
|
import "core:runtime"
|
||||||
import "core:unicode/utf16"
|
import "core:unicode/utf16"
|
||||||
|
|
||||||
is_path_separator :: proc(c: byte) -> bool {
|
is_path_separator :: proc(c: byte) -> bool {
|
||||||
@@ -327,6 +328,7 @@ get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
|||||||
|
|
||||||
|
|
||||||
exists :: proc(path: string) -> bool {
|
exists :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
attribs := win32.GetFileAttributesW(wpath)
|
attribs := win32.GetFileAttributesW(wpath)
|
||||||
|
|
||||||
@@ -334,6 +336,7 @@ exists :: proc(path: string) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_file :: proc(path: string) -> bool {
|
is_file :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
attribs := win32.GetFileAttributesW(wpath)
|
attribs := win32.GetFileAttributesW(wpath)
|
||||||
|
|
||||||
@@ -344,6 +347,7 @@ is_file :: proc(path: string) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_dir :: proc(path: string) -> bool {
|
is_dir :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
attribs := win32.GetFileAttributesW(wpath)
|
attribs := win32.GetFileAttributesW(wpath)
|
||||||
|
|
||||||
@@ -359,6 +363,8 @@ is_dir :: proc(path: string) -> bool {
|
|||||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
|
|
||||||
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
|
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
|
||||||
dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
|
dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
|
||||||
|
|
||||||
@@ -387,6 +393,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
|
|||||||
|
|
||||||
|
|
||||||
change_directory :: proc(path: string) -> (err: Errno) {
|
change_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
|
|
||||||
if !win32.SetCurrentDirectoryW(wpath) {
|
if !win32.SetCurrentDirectoryW(wpath) {
|
||||||
@@ -396,6 +403,7 @@ change_directory :: proc(path: string) -> (err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
// Mode is unused on Windows, but is needed on *nix
|
// Mode is unused on Windows, but is needed on *nix
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
|
|
||||||
@@ -407,6 +415,7 @@ make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
|||||||
|
|
||||||
|
|
||||||
remove_directory :: proc(path: string) -> (err: Errno) {
|
remove_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
|
|
||||||
if !win32.RemoveDirectoryW(wpath) {
|
if !win32.RemoveDirectoryW(wpath) {
|
||||||
@@ -479,12 +488,14 @@ fix_long_path :: proc(path: string) -> string {
|
|||||||
|
|
||||||
|
|
||||||
link :: proc(old_name, new_name: string) -> (err: Errno) {
|
link :: proc(old_name, new_name: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
n := win32.utf8_to_wstring(fix_long_path(new_name))
|
n := win32.utf8_to_wstring(fix_long_path(new_name))
|
||||||
o := win32.utf8_to_wstring(fix_long_path(old_name))
|
o := win32.utf8_to_wstring(fix_long_path(old_name))
|
||||||
return Errno(win32.CreateHardLinkW(n, o, nil))
|
return Errno(win32.CreateHardLinkW(n, o, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink :: proc(path: string) -> (err: Errno) {
|
unlink :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||||
|
|
||||||
if !win32.DeleteFileW(wpath) {
|
if !win32.DeleteFileW(wpath) {
|
||||||
@@ -496,6 +507,7 @@ unlink :: proc(path: string) -> (err: Errno) {
|
|||||||
|
|
||||||
|
|
||||||
rename :: proc(old_path, new_path: string) -> (err: Errno) {
|
rename :: proc(old_path, new_path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
|
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
|
||||||
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
|
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package os2
|
package os2
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
// get_env retrieves the value of the environment variable named by the key
|
// 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
|
// 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
|
// To distinguish between an empty value and an unset value, use lookup_env
|
||||||
// NOTE: the value will be allocated with the supplied allocator
|
// NOTE: the value will be allocated with the supplied allocator
|
||||||
get_env :: proc(key: string, allocator := context.allocator) -> string {
|
get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
|
||||||
value, _ := lookup_env(key, allocator)
|
value, _ := lookup_env(key, allocator)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -13,7 +15,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> string {
|
|||||||
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
// 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
|
// Otherwise the returned value will be empty and the boolean will be false
|
||||||
// NOTE: the value will be allocated with the supplied allocator
|
// NOTE: the value will be allocated with the supplied allocator
|
||||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||||
return _lookup_env(key, allocator)
|
return _lookup_env(key, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ clear_env :: proc() {
|
|||||||
|
|
||||||
// environ returns a copy of strings representing the environment, in the form "key=value"
|
// 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
|
// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
|
||||||
environ :: proc(allocator := context.allocator) -> []string {
|
environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||||
return _environ(allocator)
|
return _environ(allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
//+private
|
//+private
|
||||||
package os2
|
package os2
|
||||||
|
|
||||||
_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
import "core:runtime"
|
||||||
|
|
||||||
|
_get_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
|
||||||
//TODO
|
//TODO
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -20,7 +22,7 @@ _clear_env :: proc() {
|
|||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
_environ :: proc(allocator := context.allocator) -> []string {
|
_environ :: proc(allocator: runtime.Allocator) -> []string {
|
||||||
//TODO
|
//TODO
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,19 @@ _environ :: proc(allocator: runtime.Allocator) -> []string {
|
|||||||
}
|
}
|
||||||
defer win32.FreeEnvironmentStringsW(envs)
|
defer win32.FreeEnvironmentStringsW(envs)
|
||||||
|
|
||||||
r := make([dynamic]string, 0, 50, allocator)
|
n := 0
|
||||||
|
for from, i, p := 0, 0, envs; true; i += 1 {
|
||||||
|
c := ([^]u16)(p)[i]
|
||||||
|
if c == 0 {
|
||||||
|
if i <= from {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += 1
|
||||||
|
from = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := make([dynamic]string, 0, n, allocator)
|
||||||
for from, i, p := 0, 0, envs; true; i += 1 {
|
for from, i, p := 0, 0, envs; true; i += 1 {
|
||||||
c := ([^]u16)(p)[i]
|
c := ([^]u16)(p)[i]
|
||||||
if c == 0 {
|
if c == 0 {
|
||||||
|
|||||||
@@ -39,10 +39,8 @@ _file_allocator :: proc() -> runtime.Allocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
|
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
|
||||||
name_cstr, allocated := _name_to_cstring(name)
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
defer if allocated {
|
name_cstr := _name_to_cstring(name)
|
||||||
delete(name_cstr)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags_i: int
|
flags_i: int
|
||||||
switch flags & O_RDONLY|O_WRONLY|O_RDWR {
|
switch flags & O_RDONLY|O_WRONLY|O_RDWR {
|
||||||
@@ -254,7 +252,7 @@ _symlink :: proc(old_name, new_name: string) -> Error {
|
|||||||
return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
|
return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr))
|
||||||
}
|
}
|
||||||
|
|
||||||
_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) {
|
_read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (string, Error) {
|
||||||
bufsz : uint = 256
|
bufsz : uint = 256
|
||||||
buf := make([]byte, bufsz, allocator)
|
buf := make([]byte, bufsz, allocator)
|
||||||
for {
|
for {
|
||||||
@@ -272,7 +270,7 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) {
|
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
|
||||||
name_cstr, allocated := _name_to_cstring(name)
|
name_cstr, allocated := _name_to_cstring(name)
|
||||||
defer if allocated {
|
defer if allocated {
|
||||||
delete(name_cstr)
|
delete(name_cstr)
|
||||||
@@ -411,12 +409,7 @@ _is_dir_fd :: proc(fd: int) -> bool {
|
|||||||
// defined as 512, however, it is well known that paths can exceed that limit.
|
// defined as 512, however, it is well known that paths can exceed that limit.
|
||||||
// So, in theory you could have a path larger than the entire temp_allocator's
|
// So, in theory you could have a path larger than the entire temp_allocator's
|
||||||
// buffer. Therefor, any large paths will use context.allocator.
|
// buffer. Therefor, any large paths will use context.allocator.
|
||||||
_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
|
@(private="file")
|
||||||
if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
|
_temp_name_to_cstring :: proc(name: string) -> (cname: cstring) {
|
||||||
cname = strings.clone_to_cstring(name)
|
return strings.clone_to_cstring(name, context.temp_allocator)
|
||||||
allocated = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cname = strings.clone_to_cstring(name, context.temp_allocator)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package os2
|
package os2
|
||||||
|
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:runtime"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
import "core:unicode/utf8"
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
|
read_entire_file :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) {
|
||||||
f, ferr := open(name)
|
f, ferr := open(name)
|
||||||
if ferr != nil {
|
if ferr != nil {
|
||||||
return nil, ferr
|
return nil, ferr
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
|
|||||||
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
|
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
|
||||||
|
|
||||||
if res >= 0 {
|
if res >= 0 {
|
||||||
return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil
|
return strings.string_from_null_terminated_ptr(&buf[0], len(buf)), nil
|
||||||
}
|
}
|
||||||
if res != -ERANGE {
|
if res != -ERANGE {
|
||||||
return "", _get_platform_error(res)
|
return "", _get_platform_error(res)
|
||||||
@@ -229,7 +229,7 @@ _setwd :: proc(dir: string) -> Error {
|
|||||||
return _ok_or_error(unix.sys_chdir(dir_cstr))
|
return _ok_or_error(unix.sys_chdir(dir_cstr))
|
||||||
}
|
}
|
||||||
|
|
||||||
_get_full_path :: proc(fd: int, allocator := context.allocator) -> string {
|
_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string {
|
||||||
PROC_FD_PATH :: "/proc/self/fd/"
|
PROC_FD_PATH :: "/proc/self/fd/"
|
||||||
|
|
||||||
buf: [32]u8
|
buf: [32]u8
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ _Stat :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
|
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
|
||||||
return _fstat_internal(f.impl.fd, allocator)
|
return _fstat_internal(f.impl.fd, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ _fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
|
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
|
||||||
_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
|
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
|
||||||
name_cstr, allocated := _name_to_cstring(name)
|
name_cstr, allocated := _name_to_cstring(name)
|
||||||
defer if allocated {
|
defer if allocated {
|
||||||
delete(name_cstr)
|
delete(name_cstr)
|
||||||
@@ -125,7 +125,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error
|
|||||||
return _fstat_internal(fd, allocator)
|
return _fstat_internal(fd, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
|
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
|
||||||
name_cstr, allocated := _name_to_cstring(name)
|
name_cstr, allocated := _name_to_cstring(name)
|
||||||
defer if allocated {
|
defer if allocated {
|
||||||
delete(name_cstr)
|
delete(name_cstr)
|
||||||
|
|||||||
+296
-34
@@ -67,6 +67,7 @@ ENOPROTOOPT: Errno : 42 /* Protocol not available */
|
|||||||
EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */
|
EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */
|
||||||
ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */
|
ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */
|
||||||
ENOTSUP: Errno : 45 /* Operation not supported */
|
ENOTSUP: Errno : 45 /* Operation not supported */
|
||||||
|
EOPNOTSUPP:: ENOTSUP
|
||||||
EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */
|
EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */
|
||||||
EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */
|
EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */
|
||||||
EADDRINUSE: Errno : 48 /* Address already in use */
|
EADDRINUSE: Errno : 48 /* Address already in use */
|
||||||
@@ -179,6 +180,93 @@ RTLD_NODELETE :: 0x80
|
|||||||
RTLD_NOLOAD :: 0x10
|
RTLD_NOLOAD :: 0x10
|
||||||
RTLD_FIRST :: 0x100
|
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
|
||||||
|
|
||||||
|
|
||||||
// "Argv" arguments converted to Odin strings
|
// "Argv" arguments converted to Odin strings
|
||||||
args := _alloc_command_line_arguments()
|
args := _alloc_command_line_arguments()
|
||||||
@@ -224,6 +312,58 @@ Dirent :: struct {
|
|||||||
|
|
||||||
Dir :: distinct rawptr // DIR*
|
Dir :: distinct rawptr // DIR*
|
||||||
|
|
||||||
|
SOCKADDR :: struct #packed {
|
||||||
|
len: c.char,
|
||||||
|
family: c.char,
|
||||||
|
sa_data: [14]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKADDR_STORAGE_LH :: struct #packed {
|
||||||
|
len: c.char,
|
||||||
|
family: c.char,
|
||||||
|
__ss_pad1: [6]c.char,
|
||||||
|
__ss_align: i64,
|
||||||
|
__ss_pad2: [112]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in :: struct #packed {
|
||||||
|
sin_len: c.char,
|
||||||
|
sin_family: c.char,
|
||||||
|
sin_port: u16be,
|
||||||
|
sin_addr: in_addr,
|
||||||
|
sin_zero: [8]c.char,
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in6 :: struct #packed {
|
||||||
|
sin6_len: c.char,
|
||||||
|
sin6_family: c.char,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeval :: struct {
|
||||||
|
seconds: i64,
|
||||||
|
nanoseconds: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Linger :: struct {
|
||||||
|
onoff: int,
|
||||||
|
linger: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket :: distinct int
|
||||||
|
socklen_t :: c.int
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
S_IFMT :: 0o170000 // Type of file mask
|
S_IFMT :: 0o170000 // Type of file mask
|
||||||
S_IFIFO :: 0o010000 // Named pipe (fifo)
|
S_IFIFO :: 0o010000 // Named pipe (fifo)
|
||||||
@@ -277,8 +417,10 @@ foreign libc {
|
|||||||
|
|
||||||
@(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
|
@(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
|
||||||
@(link_name="close") _unix_close :: proc(handle: Handle) -> c.int ---
|
@(link_name="close") _unix_close :: proc(handle: Handle) -> c.int ---
|
||||||
@(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> 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: int) -> 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: int) -> int ---
|
@(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int ---
|
||||||
@(link_name="gettid") _unix_gettid :: proc() -> u64 ---
|
@(link_name="gettid") _unix_gettid :: proc() -> u64 ---
|
||||||
@(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---
|
@(link_name="getpagesize") _unix_getpagesize :: proc() -> i32 ---
|
||||||
@@ -316,6 +458,18 @@ foreign libc {
|
|||||||
@(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> 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="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
|
||||||
|
|
||||||
|
@(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int ---
|
||||||
|
@(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int ---
|
||||||
|
@(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int ---
|
||||||
|
@(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
|
||||||
|
@(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int ---
|
||||||
|
@(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int ---
|
||||||
|
@(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t ---
|
||||||
|
@(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
|
||||||
|
@(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t ---
|
||||||
|
@(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t ---
|
||||||
|
@(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int ---
|
||||||
|
|
||||||
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
|
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +507,7 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno
|
|||||||
flags = O_RDONLY
|
flags = O_RDONLY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
handle := _unix_open(cstr, i32(flags), u16(mode))
|
handle := _unix_open(cstr, i32(flags), u16(mode))
|
||||||
if handle == -1 {
|
if handle == -1 {
|
||||||
@@ -385,45 +540,51 @@ close :: proc(fd: Handle) -> bool {
|
|||||||
@(private)
|
@(private)
|
||||||
MAX_RW :: 0x7fffffff // The limit on Darwin is max(i32), trying to read/write more than that fails.
|
MAX_RW :: 0x7fffffff // The limit on Darwin is max(i32), trying to read/write more than that fails.
|
||||||
|
|
||||||
write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
|
write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||||
assert(fd != -1)
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
bytes_total := len(data)
|
|
||||||
bytes_written_total := 0
|
|
||||||
|
|
||||||
for bytes_written_total < bytes_total {
|
|
||||||
bytes_to_write := min(bytes_total - bytes_written_total, MAX_RW)
|
|
||||||
slice := data[bytes_written_total:bytes_written_total + bytes_to_write]
|
|
||||||
bytes_written := _unix_write(fd, raw_data(slice), bytes_to_write)
|
|
||||||
if bytes_written == -1 {
|
|
||||||
return bytes_written_total, 1
|
|
||||||
}
|
|
||||||
bytes_written_total += bytes_written
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes_written_total, 0
|
bytes_written := _unix_write(fd, raw_data(data), c.size_t(len(data)))
|
||||||
|
if bytes_written < 0 {
|
||||||
|
return -1, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return bytes_written, ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
read :: proc(fd: Handle, data: []u8) -> (int, Errno) {
|
read :: proc(fd: Handle, data: []u8) -> (int, Errno) {
|
||||||
assert(fd != -1)
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
bytes_total := len(data)
|
|
||||||
bytes_read_total := 0
|
|
||||||
|
|
||||||
for bytes_read_total < bytes_total {
|
|
||||||
bytes_to_read := min(bytes_total - bytes_read_total, MAX_RW)
|
|
||||||
slice := data[bytes_read_total:bytes_read_total + bytes_to_read]
|
|
||||||
bytes_read := _unix_read(fd, raw_data(slice), bytes_to_read)
|
|
||||||
if bytes_read == -1 {
|
|
||||||
return bytes_read_total, 1
|
|
||||||
}
|
|
||||||
if bytes_read == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
bytes_read_total += bytes_read
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes_read_total, 0
|
bytes_read := _unix_read(fd, raw_data(data), c.size_t(len(data)))
|
||||||
|
if bytes_read < 0 {
|
||||||
|
return -1, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return bytes_read, ERROR_NONE
|
||||||
|
}
|
||||||
|
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read := _unix_pread(fd, raw_data(data), c.size_t(len(data)), offset)
|
||||||
|
if bytes_read < 0 {
|
||||||
|
return -1, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return bytes_read, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_written := _unix_pwrite(fd, raw_data(data), c.size_t(len(data)), offset)
|
||||||
|
if bytes_written < 0 {
|
||||||
|
return -1, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return bytes_written, ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
||||||
@@ -508,24 +669,28 @@ is_file :: proc {is_file_path, is_file_handle}
|
|||||||
is_dir :: proc {is_dir_path, is_dir_handle}
|
is_dir :: proc {is_dir_path, is_dir_handle}
|
||||||
|
|
||||||
exists :: proc(path: string) -> bool {
|
exists :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath := strings.clone_to_cstring(path, context.temp_allocator)
|
cpath := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_access(cpath, O_RDONLY)
|
res := _unix_access(cpath, O_RDONLY)
|
||||||
return res == 0
|
return res == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
rename :: proc(old: string, new: string) -> bool {
|
rename :: proc(old: string, new: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
|
old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
|
||||||
new_cstr := strings.clone_to_cstring(new, context.temp_allocator)
|
new_cstr := strings.clone_to_cstring(new, context.temp_allocator)
|
||||||
return _unix_rename(old_cstr, new_cstr) != -1
|
return _unix_rename(old_cstr, new_cstr) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
remove :: proc(path: string) -> bool {
|
remove :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
return _unix_remove(path_cstr) != -1
|
return _unix_remove(path_cstr) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@private
|
@private
|
||||||
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
s: OS_Stat
|
s: OS_Stat
|
||||||
@@ -538,6 +703,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
s: OS_Stat
|
s: OS_Stat
|
||||||
@@ -603,6 +769,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_readlink :: proc(path: string) -> (string, Errno) {
|
_readlink :: proc(path: string) -> (string, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
bufsz : uint = 256
|
bufsz : uint = 256
|
||||||
@@ -640,6 +807,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
rel = "."
|
rel = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||||
|
|
||||||
path_ptr := _unix_realpath(rel_cstr, nil)
|
path_ptr := _unix_realpath(rel_cstr, nil)
|
||||||
@@ -655,6 +823,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
access :: proc(path: string, mask: int) -> bool {
|
access :: proc(path: string, mask: int) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
return _unix_access(cstr, mask) == 0
|
return _unix_access(cstr, mask) == 0
|
||||||
}
|
}
|
||||||
@@ -679,6 +848,7 @@ heap_free :: proc(ptr: rawptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
lookup_env :: 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)
|
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||||
cstr := _unix_getenv(path_str)
|
cstr := _unix_getenv(path_str)
|
||||||
if cstr == nil {
|
if cstr == nil {
|
||||||
@@ -710,6 +880,7 @@ get_current_directory :: proc() -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_current_directory :: proc(path: string) -> (err: Errno) {
|
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_chdir(cstr)
|
res := _unix_chdir(cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -719,6 +890,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno {
|
make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_mkdir(path_cstr, mode)
|
res := _unix_mkdir(path_cstr, mode)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -743,12 +915,14 @@ current_thread_id :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||||
handle := _unix_dlopen(cstr, flags)
|
handle := _unix_dlopen(cstr, flags)
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||||
assert(handle != nil)
|
assert(handle != nil)
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||||
proc_handle := _unix_dlsym(handle, cstr)
|
proc_handle := _unix_dlsym(handle, cstr)
|
||||||
return proc_handle
|
return proc_handle
|
||||||
@@ -793,3 +967,91 @@ _alloc_command_line_arguments :: proc() -> []string {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
|
||||||
|
result := _unix_socket(domain, type, protocol)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return Socket(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||||
|
result := _unix_connect(int(sd), addr, len)
|
||||||
|
if result < 0 {
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||||
|
result := _unix_bind(int(sd), addr, len)
|
||||||
|
if result < 0 {
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
|
||||||
|
result := _unix_accept(int(sd), rawptr(addr), len)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return Socket(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
|
||||||
|
result := _unix_listen(int(sd), backlog)
|
||||||
|
if result < 0 {
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
|
||||||
|
result := _unix_setsockopt(int(sd), level, optname, optval, optlen)
|
||||||
|
if result < 0 {
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
|
||||||
|
result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return u32(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||||
|
result := _unix_recv(int(sd), raw_data(data), len(data), flags)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return u32(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
|
||||||
|
result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return u32(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||||
|
result := _unix_send(int(sd), raw_data(data), len(data), 0)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return u32(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
|
||||||
|
result := _unix_shutdown(int(sd), how)
|
||||||
|
if result < 0 {
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|||||||
+19
-1
@@ -309,6 +309,7 @@ get_last_error :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
|
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
handle := _unix_open(cstr, c.int(flags), c.int(mode))
|
handle := _unix_open(cstr, c.int(flags), c.int(mode))
|
||||||
if handle == -1 {
|
if handle == -1 {
|
||||||
@@ -361,6 +362,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rename :: proc(old_path, new_path: string) -> Errno {
|
rename :: proc(old_path, new_path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
||||||
new_path_cstr := strings.clone_to_cstring(new_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)
|
res := _unix_rename(old_path_cstr, new_path_cstr)
|
||||||
@@ -371,6 +373,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove :: proc(path: string) -> Errno {
|
remove :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_unlink(path_cstr)
|
res := _unix_unlink(path_cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -380,6 +383,7 @@ remove :: proc(path: string) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_mkdir(path_cstr, mode)
|
res := _unix_mkdir(path_cstr, mode)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -389,6 +393,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_directory :: proc(path: string) -> Errno {
|
remove_directory :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_rmdir(path_cstr)
|
res := _unix_rmdir(path_cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -474,6 +479,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
s: OS_Stat = ---
|
s: OS_Stat = ---
|
||||||
result := _unix_lstat(cstr, &s)
|
result := _unix_lstat(cstr, &s)
|
||||||
@@ -485,6 +491,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
// deliberately uninitialized
|
// deliberately uninitialized
|
||||||
@@ -550,6 +557,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_readlink :: proc(path: string) -> (string, Errno) {
|
_readlink :: proc(path: string) -> (string, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
bufsz : uint = MAX_PATH
|
bufsz : uint = MAX_PATH
|
||||||
@@ -567,7 +576,8 @@ _readlink :: proc(path: string) -> (string, Errno) {
|
|||||||
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
|
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable()
|
|
||||||
|
return "", Errno{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX FreeBSD
|
// XXX FreeBSD
|
||||||
@@ -580,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
if rel == "" {
|
if rel == "" {
|
||||||
rel = "."
|
rel = "."
|
||||||
}
|
}
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||||
|
|
||||||
@@ -596,6 +607,8 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
result := _unix_access(cstr, c.int(mask))
|
result := _unix_access(cstr, c.int(mask))
|
||||||
if result == -1 {
|
if result == -1 {
|
||||||
@@ -626,6 +639,8 @@ heap_free :: proc(ptr: rawptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
lookup_env :: 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)
|
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||||
cstr := _unix_getenv(path_str)
|
cstr := _unix_getenv(path_str)
|
||||||
if cstr == nil {
|
if cstr == nil {
|
||||||
@@ -660,6 +675,7 @@ get_current_directory :: proc() -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_current_directory :: proc(path: string) -> (err: Errno) {
|
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_chdir(cstr)
|
res := _unix_chdir(cstr)
|
||||||
if res == -1 do return Errno(get_last_error())
|
if res == -1 do return Errno(get_last_error())
|
||||||
@@ -676,12 +692,14 @@ current_thread_id :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||||
handle := _unix_dlopen(cstr, c.int(flags))
|
handle := _unix_dlopen(cstr, c.int(flags))
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||||
assert(handle != nil)
|
assert(handle != nil)
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||||
proc_handle := _unix_dlsym(handle, cstr)
|
proc_handle := _unix_dlsym(handle, cstr)
|
||||||
return proc_handle
|
return proc_handle
|
||||||
|
|||||||
+275
-2
@@ -1,4 +1,277 @@
|
|||||||
//+js
|
//+build js
|
||||||
package os
|
package os
|
||||||
|
|
||||||
#panic("package os does not support a js target")
|
import "core:intrinsics"
|
||||||
|
import "core:runtime"
|
||||||
|
import "core:unicode/utf16"
|
||||||
|
|
||||||
|
is_path_separator :: proc(c: byte) -> bool {
|
||||||
|
return c == '/' || c == '\\'
|
||||||
|
}
|
||||||
|
|
||||||
|
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
close :: proc(fd: Handle) -> Errno {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
flush :: proc(fd: Handle) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
file_size :: proc(fd: Handle) -> (i64, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
MAX_RW :: 1<<30
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
@(private)
|
||||||
|
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
|
||||||
|
|
||||||
|
get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
||||||
|
context = runtime.default_context()
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exists :: proc(path: string) -> bool {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
is_file :: proc(path: string) -> bool {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
is_dir :: proc(path: string) -> bool {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
|
||||||
|
//@private cwd_lock := win32.SRWLOCK{} // zero is initialized
|
||||||
|
|
||||||
|
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
change_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
remove_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
is_abs :: proc(path: string) -> bool {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
fix_long_path :: proc(path: string) -> string {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
link :: proc(old_name, new_name: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink :: proc(path: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
rename :: proc(old_path, new_path: string) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate :: proc(path: string, length: i64) -> (err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
remove :: proc(name: string) -> Errno {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pipe :: proc() -> (r, w: Handle, err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle :: distinct uintptr
|
||||||
|
File_Time :: distinct u64
|
||||||
|
Errno :: distinct int
|
||||||
|
|
||||||
|
|
||||||
|
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_NONE: Errno : 0
|
||||||
|
ERROR_FILE_NOT_FOUND: Errno : 2
|
||||||
|
ERROR_PATH_NOT_FOUND: Errno : 3
|
||||||
|
ERROR_ACCESS_DENIED: Errno : 5
|
||||||
|
ERROR_INVALID_HANDLE: Errno : 6
|
||||||
|
ERROR_NOT_ENOUGH_MEMORY: Errno : 8
|
||||||
|
ERROR_NO_MORE_FILES: Errno : 18
|
||||||
|
ERROR_HANDLE_EOF: Errno : 38
|
||||||
|
ERROR_NETNAME_DELETED: Errno : 64
|
||||||
|
ERROR_FILE_EXISTS: Errno : 80
|
||||||
|
ERROR_INVALID_PARAMETER: Errno : 87
|
||||||
|
ERROR_BROKEN_PIPE: Errno : 109
|
||||||
|
ERROR_BUFFER_OVERFLOW: Errno : 111
|
||||||
|
ERROR_INSUFFICIENT_BUFFER: Errno : 122
|
||||||
|
ERROR_MOD_NOT_FOUND: Errno : 126
|
||||||
|
ERROR_PROC_NOT_FOUND: Errno : 127
|
||||||
|
ERROR_DIR_NOT_EMPTY: Errno : 145
|
||||||
|
ERROR_ALREADY_EXISTS: Errno : 183
|
||||||
|
ERROR_ENVVAR_NOT_FOUND: Errno : 203
|
||||||
|
ERROR_MORE_DATA: Errno : 234
|
||||||
|
ERROR_OPERATION_ABORTED: Errno : 995
|
||||||
|
ERROR_IO_PENDING: Errno : 997
|
||||||
|
ERROR_NOT_FOUND: Errno : 1168
|
||||||
|
ERROR_PRIVILEGE_NOT_HELD: Errno : 1314
|
||||||
|
WSAEACCES: Errno : 10013
|
||||||
|
WSAECONNRESET: Errno : 10054
|
||||||
|
|
||||||
|
// Windows reserves errors >= 1<<29 for application use
|
||||||
|
ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0
|
||||||
|
ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1
|
||||||
|
ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2
|
||||||
|
|
||||||
|
// "Argv" arguments converted to Odin strings
|
||||||
|
args := _alloc_command_line_arguments()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
last_write_time :: proc(fd: Handle) -> (File_Time, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
heap_alloc :: proc(size: int, zero_memory := true) -> rawptr {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
heap_free :: proc(ptr: rawptr) {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
get_page_size :: proc() -> int {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_processor_core_count :: proc() -> int {
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
exit :: proc "contextless" (code: int) -> ! {
|
||||||
|
context = runtime.default_context()
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
current_thread_id :: proc "contextless" () -> int {
|
||||||
|
context = runtime.default_context()
|
||||||
|
unimplemented("core:os procedure not supported on JS target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_alloc_command_line_arguments :: proc() -> []string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+363
-168
@@ -14,6 +14,7 @@ Handle :: distinct i32
|
|||||||
Pid :: distinct i32
|
Pid :: distinct i32
|
||||||
File_Time :: distinct u64
|
File_Time :: distinct u64
|
||||||
Errno :: distinct i32
|
Errno :: distinct i32
|
||||||
|
Socket :: distinct int
|
||||||
|
|
||||||
INVALID_HANDLE :: ~Handle(0)
|
INVALID_HANDLE :: ~Handle(0)
|
||||||
|
|
||||||
@@ -171,6 +172,64 @@ SEEK_DATA :: 3
|
|||||||
SEEK_HOLE :: 4
|
SEEK_HOLE :: 4
|
||||||
SEEK_MAX :: SEEK_HOLE
|
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!
|
// NOTE(zangent): These are OS specific!
|
||||||
// Do not mix these up!
|
// Do not mix these up!
|
||||||
RTLD_LAZY :: 0x001
|
RTLD_LAZY :: 0x001
|
||||||
@@ -178,6 +237,13 @@ RTLD_NOW :: 0x002
|
|||||||
RTLD_BINDING_MASK :: 0x3
|
RTLD_BINDING_MASK :: 0x3
|
||||||
RTLD_GLOBAL :: 0x100
|
RTLD_GLOBAL :: 0x100
|
||||||
|
|
||||||
|
socklen_t :: c.int
|
||||||
|
|
||||||
|
Timeval :: struct {
|
||||||
|
seconds: i64,
|
||||||
|
nanoseconds: int,
|
||||||
|
}
|
||||||
|
|
||||||
// "Argv" arguments converted to Odin strings
|
// "Argv" arguments converted to Odin strings
|
||||||
args := _alloc_command_line_arguments()
|
args := _alloc_command_line_arguments()
|
||||||
|
|
||||||
@@ -217,6 +283,102 @@ Dirent :: struct {
|
|||||||
name: [256]byte,
|
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*
|
Dir :: distinct rawptr // DIR*
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
@@ -236,13 +398,13 @@ S_IRUSR :: 0o0400 // R for owner
|
|||||||
S_IWUSR :: 0o0200 // W for owner
|
S_IWUSR :: 0o0200 // W for owner
|
||||||
S_IXUSR :: 0o0100 // X for owner
|
S_IXUSR :: 0o0100 // X for owner
|
||||||
|
|
||||||
// Read, write, execute/search by group
|
// Read, write, execute/search by group
|
||||||
S_IRWXG :: 0o0070 // RWX mask for group
|
S_IRWXG :: 0o0070 // RWX mask for group
|
||||||
S_IRGRP :: 0o0040 // R for group
|
S_IRGRP :: 0o0040 // R for group
|
||||||
S_IWGRP :: 0o0020 // W for group
|
S_IWGRP :: 0o0020 // W for group
|
||||||
S_IXGRP :: 0o0010 // X for group
|
S_IXGRP :: 0o0010 // X for group
|
||||||
|
|
||||||
// Read, write, execute/search by others
|
// Read, write, execute/search by others
|
||||||
S_IRWXO :: 0o0007 // RWX mask for other
|
S_IRWXO :: 0o0007 // RWX mask for other
|
||||||
S_IROTH :: 0o0004 // R for other
|
S_IROTH :: 0o0004 // R for other
|
||||||
S_IWOTH :: 0o0002 // W for other
|
S_IWOTH :: 0o0002 // W for other
|
||||||
@@ -270,136 +432,6 @@ AT_FDCWD :: ~uintptr(99) /* -100 */
|
|||||||
AT_REMOVEDIR :: uintptr(0x200)
|
AT_REMOVEDIR :: uintptr(0x200)
|
||||||
AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
|
AT_SYMLINK_NOFOLLOW :: uintptr(0x100)
|
||||||
|
|
||||||
_unix_personality :: proc(persona: u64) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_personality, uintptr(persona)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_fork :: proc() -> Pid {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
res := int(intrinsics.syscall(unix.SYS_fork))
|
|
||||||
} else {
|
|
||||||
res := int(intrinsics.syscall(unix.SYS_clone, unix.SIGCHLD))
|
|
||||||
}
|
|
||||||
return -1 if res < 0 else Pid(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> Handle {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
res := int(intrinsics.syscall(unix.SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
|
|
||||||
} else { // NOTE: arm64 does not have open
|
|
||||||
res := int(intrinsics.syscall(unix.SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode)))
|
|
||||||
}
|
|
||||||
return -1 if res < 0 else Handle(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_close :: proc(fd: Handle) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_close, uintptr(fd)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_read :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_read, uintptr(fd), uintptr(buf), uintptr(size)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_write :: proc(fd: Handle, buf: rawptr, size: uint) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_write, uintptr(fd), uintptr(buf), uintptr(size)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_seek :: proc(fd: Handle, offset: i64, whence: int) -> i64 {
|
|
||||||
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
|
|
||||||
return i64(intrinsics.syscall(unix.SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence)))
|
|
||||||
} else {
|
|
||||||
low := uintptr(offset & 0xFFFFFFFF)
|
|
||||||
high := uintptr(offset >> 32)
|
|
||||||
result: i64
|
|
||||||
res := i64(intrinsics.syscall(unix.SYS__llseek, uintptr(fd), high, low, uintptr(&result), uintptr(whence)))
|
|
||||||
return -1 if res < 0 else result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> int {
|
|
||||||
when ODIN_ARCH == .amd64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_stat, uintptr(rawptr(path)), uintptr(stat)))
|
|
||||||
} else when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_stat64, uintptr(rawptr(path)), uintptr(stat)))
|
|
||||||
} else { // NOTE: arm64 does not have stat
|
|
||||||
return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> int {
|
|
||||||
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_fstat, uintptr(fd), uintptr(stat)))
|
|
||||||
} else {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_fstat64, uintptr(fd), uintptr(stat)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_lstat :: proc(path: cstring, stat: ^OS_Stat) -> int {
|
|
||||||
when ODIN_ARCH == .amd64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_lstat, uintptr(rawptr(path)), uintptr(stat)))
|
|
||||||
} else when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_lstat64, uintptr(rawptr(path)), uintptr(stat)))
|
|
||||||
} else { // NOTE: arm64 does not have any lstat
|
|
||||||
return int(intrinsics.syscall(unix.SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
|
|
||||||
} else { // NOTE: arm64 does not have readlink
|
|
||||||
return int(intrinsics.syscall(unix.SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_access :: proc(path: cstring, mask: int) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_access, uintptr(rawptr(path)), uintptr(mask)))
|
|
||||||
} else { // NOTE: arm64 does not have access
|
|
||||||
return int(intrinsics.syscall(unix.SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_getcwd :: proc(buf: rawptr, size: uint) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_getcwd, uintptr(buf), uintptr(size)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_chdir :: proc(path: cstring) -> int {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_chdir, uintptr(rawptr(path))))
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_rename :: proc(old, new: cstring) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new))))
|
|
||||||
} else { // NOTE: arm64 does not have rename
|
|
||||||
return int(intrinsics.syscall(unix.SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_unlink :: proc(path: cstring) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_unlink, uintptr(rawptr(path))))
|
|
||||||
} else { // NOTE: arm64 does not have unlink
|
|
||||||
return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_rmdir :: proc(path: cstring) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_rmdir, uintptr(rawptr(path))))
|
|
||||||
} else { // NOTE: arm64 does not have rmdir
|
|
||||||
return int(intrinsics.syscall(unix.SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unix_mkdir :: proc(path: cstring, mode: u32) -> int {
|
|
||||||
when ODIN_ARCH != .arm64 {
|
|
||||||
return int(intrinsics.syscall(unix.SYS_mkdir, uintptr(rawptr(path)), uintptr(mode)))
|
|
||||||
} else { // NOTE: arm64 does not have mkdir
|
|
||||||
return int(intrinsics.syscall(unix.SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreign libc {
|
foreign libc {
|
||||||
@(link_name="__errno_location") __errno_location :: proc() -> ^int ---
|
@(link_name="__errno_location") __errno_location :: proc() -> ^int ---
|
||||||
|
|
||||||
@@ -415,6 +447,7 @@ foreign libc {
|
|||||||
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
|
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
|
||||||
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
|
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
|
||||||
|
|
||||||
|
@(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> int ---
|
||||||
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
|
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
|
||||||
@(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int ---
|
@(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int ---
|
||||||
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
|
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
|
||||||
@@ -426,6 +459,9 @@ foreign dl {
|
|||||||
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
|
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
|
||||||
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
|
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
|
||||||
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
|
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
|
||||||
|
|
||||||
|
@(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) ---
|
||||||
|
@(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) ---
|
||||||
}
|
}
|
||||||
|
|
||||||
is_path_separator :: proc(r: rune) -> bool {
|
is_path_separator :: proc(r: rune) -> bool {
|
||||||
@@ -447,7 +483,7 @@ get_last_error :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
personality :: proc(persona: u64) -> (Errno) {
|
personality :: proc(persona: u64) -> (Errno) {
|
||||||
res := _unix_personality(persona)
|
res := unix.sys_personality(persona)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
return _get_errno(res)
|
return _get_errno(res)
|
||||||
}
|
}
|
||||||
@@ -455,28 +491,48 @@ personality :: proc(persona: u64) -> (Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fork :: proc() -> (Pid, Errno) {
|
fork :: proc() -> (Pid, Errno) {
|
||||||
pid := _unix_fork()
|
pid := unix.sys_fork()
|
||||||
if pid == -1 {
|
if pid == -1 {
|
||||||
return -1, _get_errno(int(pid))
|
return -1, _get_errno(pid)
|
||||||
}
|
}
|
||||||
return pid, ERROR_NONE
|
return Pid(pid), ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
|
execvp :: proc(path: string, args: []string) -> Errno {
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
handle := _unix_open(cstr, flags, mode)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
if handle < 0 {
|
|
||||||
return INVALID_HANDLE, _get_errno(int(handle))
|
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)
|
||||||
}
|
}
|
||||||
return handle, ERROR_NONE
|
|
||||||
|
_unix_execvp(path_cstr, raw_data(args_cstrs))
|
||||||
|
return Errno(get_last_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) {
|
||||||
|
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), ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
close :: proc(fd: Handle) -> Errno {
|
close :: proc(fd: Handle) -> Errno {
|
||||||
return _get_errno(_unix_close(fd))
|
return _get_errno(unix.sys_close(int(fd)))
|
||||||
}
|
}
|
||||||
|
|
||||||
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||||
bytes_read := _unix_read(fd, &data[0], c.size_t(len(data)))
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read := unix.sys_read(int(fd), raw_data(data), len(data))
|
||||||
if bytes_read < 0 {
|
if bytes_read < 0 {
|
||||||
return -1, _get_errno(bytes_read)
|
return -1, _get_errno(bytes_read)
|
||||||
}
|
}
|
||||||
@@ -487,50 +543,78 @@ write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
|||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return 0, ERROR_NONE
|
return 0, ERROR_NONE
|
||||||
}
|
}
|
||||||
bytes_written := _unix_write(fd, &data[0], uint(len(data)))
|
|
||||||
|
bytes_written := unix.sys_write(int(fd), raw_data(data), len(data))
|
||||||
if bytes_written < 0 {
|
if bytes_written < 0 {
|
||||||
return -1, _get_errno(bytes_written)
|
return -1, _get_errno(bytes_written)
|
||||||
}
|
}
|
||||||
return int(bytes_written), ERROR_NONE
|
return bytes_written, ERROR_NONE
|
||||||
|
}
|
||||||
|
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read := unix.sys_pread(int(fd), raw_data(data), len(data), offset)
|
||||||
|
if bytes_read < 0 {
|
||||||
|
return -1, _get_errno(bytes_read)
|
||||||
|
}
|
||||||
|
return bytes_read, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_written := unix.sys_pwrite(int(fd), raw_data(data), uint(len(data)), offset)
|
||||||
|
if bytes_written < 0 {
|
||||||
|
return -1, _get_errno(bytes_written)
|
||||||
|
}
|
||||||
|
return bytes_written, ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
||||||
res := _unix_seek(fd, offset, whence)
|
res := unix.sys_lseek(int(fd), offset, whence)
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
return -1, _get_errno(int(res))
|
return -1, _get_errno(int(res))
|
||||||
}
|
}
|
||||||
return res, ERROR_NONE
|
return i64(res), ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
file_size :: proc(fd: Handle) -> (i64, Errno) {
|
file_size :: proc(fd: Handle) -> (i64, Errno) {
|
||||||
// deliberately uninitialized; the syscall fills this buffer for us
|
// deliberately uninitialized; the syscall fills this buffer for us
|
||||||
s: OS_Stat = ---
|
s: OS_Stat = ---
|
||||||
result := _unix_fstat(fd, &s)
|
result := unix.sys_fstat(int(fd), rawptr(&s))
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return 0, _get_errno(result)
|
return 0, _get_errno(result)
|
||||||
}
|
}
|
||||||
return max(s.size, 0), ERROR_NONE
|
return max(s.size, 0), ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
rename :: proc(old_path, new_path: string) -> Errno {
|
rename :: proc(old_path, new_path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
||||||
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
|
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
|
||||||
return _get_errno(_unix_rename(old_path_cstr, new_path_cstr))
|
return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr))
|
||||||
}
|
}
|
||||||
|
|
||||||
remove :: proc(path: string) -> Errno {
|
remove :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
return _get_errno(_unix_unlink(path_cstr))
|
return _get_errno(unix.sys_unlink(path_cstr))
|
||||||
}
|
}
|
||||||
|
|
||||||
make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
|
make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
return _get_errno(_unix_mkdir(path_cstr, mode))
|
return _get_errno(unix.sys_mkdir(path_cstr, uint(mode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
remove_directory :: proc(path: string) -> Errno {
|
remove_directory :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
return _get_errno(_unix_rmdir(path_cstr))
|
return _get_errno(unix.sys_rmdir(path_cstr))
|
||||||
}
|
}
|
||||||
|
|
||||||
is_file_handle :: proc(fd: Handle) -> bool {
|
is_file_handle :: proc(fd: Handle) -> bool {
|
||||||
@@ -582,8 +666,9 @@ is_file :: proc {is_file_path, is_file_handle}
|
|||||||
is_dir :: proc {is_dir_path, is_dir_handle}
|
is_dir :: proc {is_dir_path, is_dir_handle}
|
||||||
|
|
||||||
exists :: proc(path: string) -> bool {
|
exists :: proc(path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath := strings.clone_to_cstring(path, context.temp_allocator)
|
cpath := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_access(cpath, O_RDONLY)
|
res := unix.sys_access(cpath, O_RDONLY)
|
||||||
return res == 0
|
return res == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,11 +702,12 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
// deliberately uninitialized; the syscall fills this buffer for us
|
// deliberately uninitialized; the syscall fills this buffer for us
|
||||||
s: OS_Stat = ---
|
s: OS_Stat = ---
|
||||||
result := _unix_stat(cstr, &s)
|
result := unix.sys_stat(cstr, &s)
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return s, _get_errno(result)
|
return s, _get_errno(result)
|
||||||
}
|
}
|
||||||
@@ -630,11 +716,12 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
// deliberately uninitialized; the syscall fills this buffer for us
|
// deliberately uninitialized; the syscall fills this buffer for us
|
||||||
s: OS_Stat = ---
|
s: OS_Stat = ---
|
||||||
result := _unix_lstat(cstr, &s)
|
result := unix.sys_lstat(cstr, &s)
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return s, _get_errno(result)
|
return s, _get_errno(result)
|
||||||
}
|
}
|
||||||
@@ -645,7 +732,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
|||||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
|
_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) {
|
||||||
// deliberately uninitialized; the syscall fills this buffer for us
|
// deliberately uninitialized; the syscall fills this buffer for us
|
||||||
s: OS_Stat = ---
|
s: OS_Stat = ---
|
||||||
result := _unix_fstat(fd, &s)
|
result := unix.sys_fstat(int(fd), rawptr(&s))
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return s, _get_errno(result)
|
return s, _get_errno(result)
|
||||||
}
|
}
|
||||||
@@ -697,12 +784,13 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_readlink :: proc(path: string) -> (string, Errno) {
|
_readlink :: proc(path: string) -> (string, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
bufsz : uint = 256
|
bufsz : uint = 256
|
||||||
buf := make([]byte, bufsz)
|
buf := make([]byte, bufsz)
|
||||||
for {
|
for {
|
||||||
rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
|
rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz)
|
||||||
if rc < 0 {
|
if rc < 0 {
|
||||||
delete(buf)
|
delete(buf)
|
||||||
return "", _get_errno(rc)
|
return "", _get_errno(rc)
|
||||||
@@ -732,6 +820,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
if rel == "" {
|
if rel == "" {
|
||||||
rel = "."
|
rel = "."
|
||||||
}
|
}
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
|
||||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||||
|
|
||||||
@@ -748,8 +837,9 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
result := _unix_access(cstr, mask)
|
result := unix.sys_access(cstr, mask)
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
return false, _get_errno(result)
|
return false, _get_errno(result)
|
||||||
}
|
}
|
||||||
@@ -778,6 +868,7 @@ heap_free :: proc(ptr: rawptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
lookup_env :: 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)
|
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||||
cstr := _unix_getenv(path_str)
|
cstr := _unix_getenv(path_str)
|
||||||
@@ -793,6 +884,7 @@ get_env :: proc(key: string, allocator := context.allocator) -> (value: string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_env :: proc(key, value: string) -> Errno {
|
set_env :: proc(key, value: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator)
|
s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator)
|
||||||
res := _unix_putenv(strings.unsafe_string_to_cstring(s))
|
res := _unix_putenv(strings.unsafe_string_to_cstring(s))
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
@@ -802,6 +894,7 @@ set_env :: proc(key, value: string) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unset_env :: proc(key: string) -> Errno {
|
unset_env :: proc(key: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
s := strings.clone_to_cstring(key, context.temp_allocator)
|
s := strings.clone_to_cstring(key, context.temp_allocator)
|
||||||
res := _unix_putenv(s)
|
res := _unix_putenv(s)
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
@@ -817,10 +910,10 @@ get_current_directory :: proc() -> string {
|
|||||||
page_size := get_page_size()
|
page_size := get_page_size()
|
||||||
buf := make([dynamic]u8, page_size)
|
buf := make([dynamic]u8, page_size)
|
||||||
for {
|
for {
|
||||||
#no_bounds_check res := _unix_getcwd(&buf[0], uint(len(buf)))
|
#no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf)))
|
||||||
|
|
||||||
if res >= 0 {
|
if res >= 0 {
|
||||||
return strings.string_from_nul_terminated_ptr(&buf[0], len(buf))
|
return strings.string_from_null_terminated_ptr(&buf[0], len(buf))
|
||||||
}
|
}
|
||||||
if _get_errno(res) != ERANGE {
|
if _get_errno(res) != ERANGE {
|
||||||
delete(buf)
|
delete(buf)
|
||||||
@@ -832,8 +925,9 @@ get_current_directory :: proc() -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_current_directory :: proc(path: string) -> (err: Errno) {
|
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_chdir(cstr)
|
res := unix.sys_chdir(cstr)
|
||||||
if res < 0 {
|
if res < 0 {
|
||||||
return _get_errno(res)
|
return _get_errno(res)
|
||||||
}
|
}
|
||||||
@@ -850,12 +944,14 @@ current_thread_id :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||||
handle := _unix_dlopen(cstr, c.int(flags))
|
handle := _unix_dlopen(cstr, c.int(flags))
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||||
assert(handle != nil)
|
assert(handle != nil)
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||||
proc_handle := _unix_dlsym(handle, cstr)
|
proc_handle := _unix_dlsym(handle, cstr)
|
||||||
return proc_handle
|
return proc_handle
|
||||||
@@ -892,3 +988,102 @@ _alloc_command_line_arguments :: proc() -> []string {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) {
|
||||||
|
result := unix.sys_socket(domain, type, protocol)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, _get_errno(result)
|
||||||
|
}
|
||||||
|
return Socket(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||||
|
result := unix.sys_bind(int(sd), addr, len)
|
||||||
|
if result < 0 {
|
||||||
|
return _get_errno(result)
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) {
|
||||||
|
result := unix.sys_connect(int(sd), addr, len)
|
||||||
|
if result < 0 {
|
||||||
|
return _get_errno(result)
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) {
|
||||||
|
result := unix.sys_accept(int(sd), rawptr(addr), len)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, _get_errno(result)
|
||||||
|
}
|
||||||
|
return Socket(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
listen :: proc(sd: Socket, backlog: int) -> (Errno) {
|
||||||
|
result := unix.sys_listen(int(sd), backlog)
|
||||||
|
if result < 0 {
|
||||||
|
return _get_errno(result)
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) {
|
||||||
|
result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen)
|
||||||
|
if result < 0 {
|
||||||
|
return _get_errno(result)
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) {
|
||||||
|
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), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||||
|
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), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) {
|
||||||
|
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), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) {
|
||||||
|
result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, _get_errno(int(result))
|
||||||
|
}
|
||||||
|
return u32(result), ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown :: proc(sd: Socket, how: int) -> (Errno) {
|
||||||
|
result := unix.sys_shutdown(int(sd), how)
|
||||||
|
if result < 0 {
|
||||||
|
return _get_errno(result)
|
||||||
|
}
|
||||||
|
return ERROR_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) {
|
||||||
|
result := unix.sys_fcntl(fd, cmd, arg)
|
||||||
|
if result < 0 {
|
||||||
|
return 0, _get_errno(result)
|
||||||
|
}
|
||||||
|
return result, ERROR_NONE
|
||||||
|
}
|
||||||
+14
-1
@@ -308,6 +308,7 @@ fork :: proc() -> (Pid, Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
|
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
handle := _unix_open(cstr, c.int(flags), c.int(mode))
|
handle := _unix_open(cstr, c.int(flags), c.int(mode))
|
||||||
if handle == -1 {
|
if handle == -1 {
|
||||||
@@ -360,6 +361,7 @@ file_size :: proc(fd: Handle) -> (i64, Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rename :: proc(old_path, new_path: string) -> Errno {
|
rename :: proc(old_path, new_path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
||||||
new_path_cstr := strings.clone_to_cstring(new_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)
|
res := _unix_rename(old_path_cstr, new_path_cstr)
|
||||||
@@ -370,6 +372,7 @@ rename :: proc(old_path, new_path: string) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove :: proc(path: string) -> Errno {
|
remove :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_unlink(path_cstr)
|
res := _unix_unlink(path_cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -379,6 +382,7 @@ remove :: proc(path: string) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_mkdir(path_cstr, mode)
|
res := _unix_mkdir(path_cstr, mode)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -388,6 +392,7 @@ make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_directory :: proc(path: string) -> Errno {
|
remove_directory :: proc(path: string) -> Errno {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_rmdir(path_cstr)
|
res := _unix_rmdir(path_cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -473,6 +478,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
_stat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
// deliberately uninitialized
|
// deliberately uninitialized
|
||||||
@@ -486,6 +492,7 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) {
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
_lstat :: proc(path: string) -> (OS_Stat, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
// deliberately uninitialized
|
// deliberately uninitialized
|
||||||
@@ -552,6 +559,7 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool)
|
|||||||
|
|
||||||
@private
|
@private
|
||||||
_readlink :: proc(path: string) -> (string, Errno) {
|
_readlink :: proc(path: string) -> (string, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
|
|
||||||
bufsz : uint = MAX_PATH
|
bufsz : uint = MAX_PATH
|
||||||
@@ -569,7 +577,6 @@ _readlink :: proc(path: string) -> (string, Errno) {
|
|||||||
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
|
return strings.string_from_ptr(&buf[0], rc), ERROR_NONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX OpenBSD
|
// XXX OpenBSD
|
||||||
@@ -583,6 +590,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
rel = "."
|
rel = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||||
|
|
||||||
path_ptr := _unix_realpath(rel_cstr, nil)
|
path_ptr := _unix_realpath(rel_cstr, nil)
|
||||||
@@ -598,6 +606,7 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
access :: proc(path: string, mask: int) -> (bool, Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_access(cstr, c.int(mask))
|
res := _unix_access(cstr, c.int(mask))
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -628,6 +637,7 @@ heap_free :: proc(ptr: rawptr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
lookup_env :: 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)
|
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||||
cstr := _unix_getenv(path_str)
|
cstr := _unix_getenv(path_str)
|
||||||
if cstr == nil {
|
if cstr == nil {
|
||||||
@@ -658,6 +668,7 @@ get_current_directory :: proc() -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set_current_directory :: proc(path: string) -> (err: Errno) {
|
set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||||
res := _unix_chdir(cstr)
|
res := _unix_chdir(cstr)
|
||||||
if res == -1 {
|
if res == -1 {
|
||||||
@@ -676,12 +687,14 @@ current_thread_id :: proc "contextless" () -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||||
handle := _unix_dlopen(cstr, c.int(flags))
|
handle := _unix_dlopen(cstr, c.int(flags))
|
||||||
return handle
|
return handle
|
||||||
}
|
}
|
||||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||||
assert(handle != nil)
|
assert(handle != nil)
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||||
proc_handle := _unix_dlsym(handle, cstr)
|
proc_handle := _unix_dlsym(handle, cstr)
|
||||||
return proc_handle
|
return proc_handle
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ _processor_core_count :: proc() -> int {
|
|||||||
|
|
||||||
thread_count := 0
|
thread_count := 0
|
||||||
if !result && win32.GetLastError() == 122 && length > 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)
|
processors := make([]win32.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length, context.temp_allocator)
|
||||||
|
|
||||||
result = win32.GetLogicalProcessorInformation(&processors[0], &length)
|
result = win32.GetLogicalProcessorInformation(&processors[0], &length)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package os
|
package os
|
||||||
|
|
||||||
import "core:time"
|
import "core:time"
|
||||||
|
import "core:runtime"
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
@@ -11,6 +12,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
|
|||||||
if name == "" {
|
if name == "" {
|
||||||
name = "."
|
name = "."
|
||||||
}
|
}
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
p := win32.utf8_to_utf16(name, context.temp_allocator)
|
p := win32.utf8_to_utf16(name, context.temp_allocator)
|
||||||
buf := make([dynamic]u16, 100)
|
buf := make([dynamic]u16, 100)
|
||||||
defer delete(buf)
|
defer delete(buf)
|
||||||
@@ -36,6 +38,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
|
|||||||
|
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
|
|
||||||
wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
|
wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
|
||||||
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
|
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
|
||||||
@@ -132,14 +135,15 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
|
|||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
|
cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
|
||||||
buf, err := cleanpath_from_handle_u16(fd)
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||||
|
buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator)
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
|
return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
|
||||||
}
|
}
|
||||||
@(private)
|
@(private)
|
||||||
cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
|
cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
|
||||||
if fd == 0 {
|
if fd == 0 {
|
||||||
return nil, ERROR_INVALID_HANDLE
|
return nil, ERROR_INVALID_HANDLE
|
||||||
}
|
}
|
||||||
@@ -149,7 +153,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
|
|||||||
if n == 0 {
|
if n == 0 {
|
||||||
return nil, Errno(win32.GetLastError())
|
return nil, Errno(win32.GetLastError())
|
||||||
}
|
}
|
||||||
buf := make([]u16, max(n, win32.DWORD(260))+1, context.temp_allocator)
|
buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
|
||||||
buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
|
buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0)
|
||||||
return buf[:buf_len], ERROR_NONE
|
return buf[:buf_len], ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er
|
|||||||
//
|
//
|
||||||
// glob ignores file system errors
|
// glob ignores file system errors
|
||||||
//
|
//
|
||||||
|
|
||||||
glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) {
|
glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -261,6 +260,8 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`.
|
||||||
_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) {
|
_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ is_slash :: proc(c: byte) -> bool {
|
|||||||
return c == '\\' || c == '/'
|
return c == '\\' || c == '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Splits path immediate following the last separator; separating the path into a directory and file.
|
||||||
|
// If no separator is found, `dir` will be empty and `path` set to `path`.
|
||||||
split :: proc(path: string) -> (dir, file: string) {
|
split :: proc(path: string) -> (dir, file: string) {
|
||||||
vol := volume_name(path)
|
vol := volume_name(path)
|
||||||
i := len(path) - 1
|
i := len(path) - 1
|
||||||
@@ -29,10 +31,18 @@ split :: proc(path: string) -> (dir, file: string) {
|
|||||||
return path[:i+1], path[i+1:]
|
return path[:i+1], path[i+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 {
|
volume_name :: proc(path: string) -> string {
|
||||||
return path[:volume_name_len(path)]
|
return path[:volume_name_len(path)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the length of the volume name in bytes.
|
||||||
volume_name_len :: proc(path: string) -> int {
|
volume_name_len :: proc(path: string) -> int {
|
||||||
if ODIN_OS == .Windows {
|
if ODIN_OS == .Windows {
|
||||||
if len(path) < 2 {
|
if len(path) < 2 {
|
||||||
@@ -74,7 +84,7 @@ volume_name_len :: proc(path: string) -> int {
|
|||||||
/*
|
/*
|
||||||
Gets the file name and extension from a path.
|
Gets the file name and extension from a path.
|
||||||
|
|
||||||
i.e:
|
e.g.
|
||||||
'path/to/name.tar.gz' -> 'name.tar.gz'
|
'path/to/name.tar.gz' -> 'name.tar.gz'
|
||||||
'path/to/name.txt' -> 'name.txt'
|
'path/to/name.txt' -> 'name.txt'
|
||||||
'path/to/name' -> 'name'
|
'path/to/name' -> 'name'
|
||||||
@@ -114,7 +124,7 @@ base :: proc(path: string) -> string {
|
|||||||
Only the last dot is considered when splitting the file extension.
|
Only the last dot is considered when splitting the file extension.
|
||||||
See `short_stem`.
|
See `short_stem`.
|
||||||
|
|
||||||
i.e:
|
e.g.
|
||||||
'name.tar.gz' -> 'name.tar'
|
'name.tar.gz' -> 'name.tar'
|
||||||
'name.txt' -> 'name'
|
'name.txt' -> 'name'
|
||||||
|
|
||||||
@@ -147,7 +157,7 @@ stem :: proc(path: string) -> string {
|
|||||||
|
|
||||||
The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
|
The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
|
||||||
|
|
||||||
i.e:
|
e.g.
|
||||||
'name.tar.gz' -> 'name'
|
'name.tar.gz' -> 'name'
|
||||||
'name.txt' -> 'name'
|
'name.txt' -> 'name'
|
||||||
|
|
||||||
@@ -170,7 +180,7 @@ short_stem :: proc(path: string) -> string {
|
|||||||
Only the last dot is considered when splitting the file extension.
|
Only the last dot is considered when splitting the file extension.
|
||||||
See `long_ext`.
|
See `long_ext`.
|
||||||
|
|
||||||
i.e:
|
e.g.
|
||||||
'name.tar.gz' -> '.gz'
|
'name.tar.gz' -> '.gz'
|
||||||
'name.txt' -> '.txt'
|
'name.txt' -> '.txt'
|
||||||
|
|
||||||
@@ -193,7 +203,7 @@ ext :: proc(path: string) -> string {
|
|||||||
|
|
||||||
The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
|
The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
|
||||||
|
|
||||||
i.e:
|
e.g.
|
||||||
'name.tar.gz' -> '.tar.gz'
|
'name.tar.gz' -> '.tar.gz'
|
||||||
'name.txt' -> '.txt'
|
'name.txt' -> '.txt'
|
||||||
|
|
||||||
@@ -219,6 +229,21 @@ long_ext :: proc(path: string) -> string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns the shortest path name equivalent to `path` through solely lexical processing.
|
||||||
|
It applies the folliwng rules until none of them can be applied:
|
||||||
|
|
||||||
|
* Replace multiple separators with a single one
|
||||||
|
* Remove each current directory (`.`) path name element
|
||||||
|
* Remove each inner parent directory (`..`) path and the preceding paths
|
||||||
|
* Remove `..` that begin at the root of a path
|
||||||
|
* All possible separators are replaced with the OS specific separator
|
||||||
|
|
||||||
|
The return path ends in a slash only if it represents the root of a directory (`C:\` on Windows and `/` on *nix systems).
|
||||||
|
|
||||||
|
If the result of the path is an empty string, the returned path with be `"."`.
|
||||||
|
|
||||||
|
*/
|
||||||
clean :: proc(path: string, allocator := context.allocator) -> string {
|
clean :: proc(path: string, allocator := context.allocator) -> string {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
@@ -299,6 +324,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string {
|
|||||||
return cleaned
|
return cleaned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character.
|
||||||
from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
|
from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
|
||||||
if SEPARATOR == '/' {
|
if SEPARATOR == '/' {
|
||||||
return path, false
|
return path, false
|
||||||
@@ -306,6 +332,7 @@ from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: s
|
|||||||
return strings.replace_all(path, "/", SEPARATOR_STRING, allocator)
|
return strings.replace_all(path, "/", SEPARATOR_STRING, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the result of replacing each OS specific separator with a forward slash `/` character.
|
||||||
to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
|
to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
|
||||||
if SEPARATOR == '/' {
|
if SEPARATOR == '/' {
|
||||||
return path, false
|
return path, false
|
||||||
@@ -320,6 +347,13 @@ Relative_Error :: enum {
|
|||||||
Cannot_Relate,
|
Cannot_Relate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a relative path that is lexically equivalent to the `target_path` when joined with the `base_path` with an OS specific separator.
|
||||||
|
|
||||||
|
e.g. `join(base_path, rel(base_path, target_path))` is equivalent to `target_path`
|
||||||
|
|
||||||
|
On failure, the `Relative_Error` will be state it cannot compute the necessary relative path.
|
||||||
|
*/
|
||||||
rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) {
|
rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
base_clean, target_clean := clean(base_path), clean(target_path)
|
base_clean, target_clean := clean(base_path), clean(target_path)
|
||||||
@@ -398,6 +432,11 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (
|
|||||||
return target[t0:], .None
|
return target[t0:], .None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns all but the last element path, usually the path's directory. Once the final element has been removed,
|
||||||
|
`dir` calls `clean` on the path and trailing separators are removed. If the path consists purely of separators,
|
||||||
|
then `"."` is returned.
|
||||||
|
*/
|
||||||
dir :: proc(path: string, allocator := context.allocator) -> string {
|
dir :: proc(path: string, allocator := context.allocator) -> string {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
vol := volume_name(path)
|
vol := volume_name(path)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ when ODIN_OS == .Darwin {
|
|||||||
foreign import libc "system:c"
|
foreign import libc "system:c"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
SEPARATOR :: '/'
|
SEPARATOR :: '/'
|
||||||
@@ -41,6 +42,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
|
|||||||
join :: proc(elems: []string, allocator := context.allocator) -> string {
|
join :: proc(elems: []string, allocator := context.allocator) -> string {
|
||||||
for e, i in elems {
|
for e, i in elems {
|
||||||
if e != "" {
|
if e != "" {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator)
|
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator)
|
||||||
return clean(p, allocator)
|
return clean(p, allocator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package filepath
|
package filepath
|
||||||
|
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
import "core:runtime"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import win32 "core:sys/windows"
|
import win32 "core:sys/windows"
|
||||||
|
|
||||||
@@ -60,25 +61,25 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p := win32.utf8_to_utf16(name, ta)
|
p := win32.utf8_to_utf16(name, ta)
|
||||||
buf := make([dynamic]u16, 100, ta)
|
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
|
||||||
for {
|
if n == 0 {
|
||||||
n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
|
return "", os.Errno(win32.GetLastError())
|
||||||
if n == 0 {
|
|
||||||
delete(buf)
|
|
||||||
return "", os.Errno(win32.GetLastError())
|
|
||||||
}
|
|
||||||
if n <= u32(len(buf)) {
|
|
||||||
return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
|
|
||||||
}
|
|
||||||
resize(&buf, len(buf)*2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
buf := make([]u16, n, ta)
|
||||||
|
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
|
||||||
|
if n == 0 {
|
||||||
|
delete(buf)
|
||||||
|
return "", os.Errno(win32.GetLastError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
|
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
|
||||||
full_path, err := temp_full_path(path)
|
full_path, err := temp_full_path(path)
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return "", false
|
return "", false
|
||||||
@@ -100,6 +101,8 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
|
|||||||
join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string {
|
join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
|
||||||
|
|
||||||
if len(elems[0]) == 2 && elems[0][1] == ':' {
|
if len(elems[0]) == 2 && elems[0][1] == ':' {
|
||||||
i := 1
|
i := 1
|
||||||
for ; i < len(elems); i += 1 {
|
for ; i < len(elems); i += 1 {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// To manipulate operating system specific paths, use the path/filepath package
|
// To manipulate operating system specific paths, use the path/filepath package
|
||||||
package slashpath
|
package slashpath
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
// is_separator checks whether the byte is a valid separator character
|
// is_separator checks whether the byte is a valid separator character
|
||||||
@@ -150,8 +151,9 @@ join :: proc(elems: []string, allocator := context.allocator) -> string {
|
|||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
for elem, i in elems {
|
for elem, i in elems {
|
||||||
if elem != "" {
|
if elem != "" {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||||
s := strings.join(elems[i:], "/", context.temp_allocator)
|
s := strings.join(elems[i:], "/", context.temp_allocator)
|
||||||
return clean(s)
|
return clean(s, allocator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
import "core:prof/spall"
|
||||||
|
|
||||||
|
spall_ctx: spall.Context
|
||||||
|
spall_buffer: spall.Buffer
|
||||||
|
|
||||||
|
foo :: proc() {
|
||||||
|
spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
spall_ctx = spall.context_create("trace_test.spall")
|
||||||
|
defer spall.context_destroy(&spall_ctx)
|
||||||
|
|
||||||
|
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
|
||||||
|
spall_buffer = spall.buffer_create(buffer_backing)
|
||||||
|
defer spall.buffer_destroy(&spall_ctx, &spall_buffer)
|
||||||
|
|
||||||
|
spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure)
|
||||||
|
|
||||||
|
for i := 0; i < 9001; i += 1 {
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package spall
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
package spall
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
import "core:time"
|
||||||
|
import "core:intrinsics"
|
||||||
|
import "core:mem"
|
||||||
|
|
||||||
|
// File Format
|
||||||
|
|
||||||
|
MANUAL_MAGIC :: u64le(0x0BADF00D)
|
||||||
|
|
||||||
|
Manual_Header :: struct #packed {
|
||||||
|
magic: u64le,
|
||||||
|
version: u64le,
|
||||||
|
timestamp_scale: f64le,
|
||||||
|
reserved: u64le,
|
||||||
|
}
|
||||||
|
|
||||||
|
Manual_Event_Type :: enum u8 {
|
||||||
|
Invalid = 0,
|
||||||
|
|
||||||
|
Begin = 3,
|
||||||
|
End = 4,
|
||||||
|
Instant = 5,
|
||||||
|
|
||||||
|
Pad_Skip = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
Begin_Event :: struct #packed {
|
||||||
|
type: Manual_Event_Type,
|
||||||
|
category: u8,
|
||||||
|
pid: u32le,
|
||||||
|
tid: u32le,
|
||||||
|
ts: f64le,
|
||||||
|
name_len: u8,
|
||||||
|
args_len: u8,
|
||||||
|
}
|
||||||
|
BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255
|
||||||
|
|
||||||
|
End_Event :: struct #packed {
|
||||||
|
type: Manual_Event_Type,
|
||||||
|
pid: u32le,
|
||||||
|
tid: u32le,
|
||||||
|
ts: f64le,
|
||||||
|
}
|
||||||
|
|
||||||
|
Pad_Skip :: struct #packed {
|
||||||
|
type: Manual_Event_Type,
|
||||||
|
size: u32le,
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Interface
|
||||||
|
|
||||||
|
Context :: struct {
|
||||||
|
precise_time: bool,
|
||||||
|
timestamp_scale: f64,
|
||||||
|
fd: os.Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer :: struct {
|
||||||
|
data: []u8,
|
||||||
|
head: int,
|
||||||
|
tid: u32,
|
||||||
|
pid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
BUFFER_DEFAULT_SIZE :: 0x10_0000
|
||||||
|
|
||||||
|
|
||||||
|
context_create :: proc(filename: string) -> (ctx: Context, ok: bool) #optional_ok {
|
||||||
|
fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
|
||||||
|
if err != os.ERROR_NONE {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.fd = fd
|
||||||
|
|
||||||
|
freq, freq_ok := time.tsc_frequency()
|
||||||
|
ctx.precise_time = freq_ok
|
||||||
|
ctx.timestamp_scale = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1
|
||||||
|
|
||||||
|
temp := [size_of(Manual_Header)]u8{}
|
||||||
|
_build_header(temp[:], ctx.timestamp_scale)
|
||||||
|
os.write(ctx.fd, temp[:])
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context_destroy :: proc(ctx: ^Context) {
|
||||||
|
if ctx == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.close(ctx.fd)
|
||||||
|
ctx^ = Context{}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok {
|
||||||
|
assert(len(data) >= 1024)
|
||||||
|
buffer.data = data
|
||||||
|
buffer.tid = tid
|
||||||
|
buffer.pid = pid
|
||||||
|
buffer.head = 0
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) {
|
||||||
|
start := _trace_now(ctx)
|
||||||
|
os.write(ctx.fd, buffer.data[:buffer.head])
|
||||||
|
buffer.head = 0
|
||||||
|
end := _trace_now(ctx)
|
||||||
|
|
||||||
|
buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid)
|
||||||
|
buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) {
|
||||||
|
buffer_flush(ctx, buffer)
|
||||||
|
|
||||||
|
buffer^ = Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@(deferred_in=_scoped_buffer_end)
|
||||||
|
SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool {
|
||||||
|
_buffer_begin(ctx, buffer, name, args, location)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
_scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) {
|
||||||
|
_buffer_end(ctx, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_trace_now :: proc "contextless" (ctx: ^Context) -> f64 {
|
||||||
|
if !ctx.precise_time {
|
||||||
|
return f64(time.tick_now()._nsec) / 1_000
|
||||||
|
}
|
||||||
|
|
||||||
|
return f64(intrinsics.read_cycle_counter())
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok {
|
||||||
|
header_size = size_of(Manual_Header)
|
||||||
|
if header_size > len(buffer) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr := (^Manual_Header)(raw_data(buffer))
|
||||||
|
hdr.magic = MANUAL_MAGIC
|
||||||
|
hdr.version = 1
|
||||||
|
hdr.timestamp_scale = f64le(timestamp_scale)
|
||||||
|
hdr.reserved = 0
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
|
||||||
|
ev := (^Begin_Event)(raw_data(buffer))
|
||||||
|
name_len := min(len(name), 255)
|
||||||
|
args_len := min(len(args), 255)
|
||||||
|
|
||||||
|
event_size = size_of(Begin_Event) + name_len + args_len
|
||||||
|
if event_size > len(buffer) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.type = .Begin
|
||||||
|
ev.pid = u32le(pid)
|
||||||
|
ev.tid = u32le(tid)
|
||||||
|
ev.ts = f64le(ts)
|
||||||
|
ev.name_len = u8(name_len)
|
||||||
|
ev.args_len = u8(args_len)
|
||||||
|
mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len)
|
||||||
|
mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len)
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok {
|
||||||
|
ev := (^End_Event)(raw_data(buffer))
|
||||||
|
event_size = size_of(End_Event)
|
||||||
|
if event_size > len(buffer) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.type = .End
|
||||||
|
ev.pid = u32le(pid)
|
||||||
|
ev.tid = u32le(tid)
|
||||||
|
ev.ts = f64le(ts)
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) {
|
||||||
|
if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) {
|
||||||
|
buffer_flush(ctx, buffer)
|
||||||
|
}
|
||||||
|
name := location.procedure if name == "" else name
|
||||||
|
buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) {
|
||||||
|
ts := _trace_now(ctx)
|
||||||
|
|
||||||
|
if buffer.head + size_of(End_Event) > len(buffer.data) {
|
||||||
|
buffer_flush(ctx, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid)
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@ Type_Info_Array :: runtime.Type_Info_Array
|
|||||||
Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
|
Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
|
||||||
Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array
|
Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array
|
||||||
Type_Info_Slice :: runtime.Type_Info_Slice
|
Type_Info_Slice :: runtime.Type_Info_Slice
|
||||||
Type_Info_Tuple :: runtime.Type_Info_Tuple
|
Type_Info_Parameters :: runtime.Type_Info_Parameters
|
||||||
|
Type_Info_Tuple :: runtime.Type_Info_Parameters
|
||||||
Type_Info_Struct :: runtime.Type_Info_Struct
|
Type_Info_Struct :: runtime.Type_Info_Struct
|
||||||
Type_Info_Union :: runtime.Type_Info_Union
|
Type_Info_Union :: runtime.Type_Info_Union
|
||||||
Type_Info_Enum :: runtime.Type_Info_Enum
|
Type_Info_Enum :: runtime.Type_Info_Enum
|
||||||
@@ -96,7 +97,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
|
|||||||
case Type_Info_Enumerated_Array: return .Enumerated_Array
|
case Type_Info_Enumerated_Array: return .Enumerated_Array
|
||||||
case Type_Info_Dynamic_Array: return .Dynamic_Array
|
case Type_Info_Dynamic_Array: return .Dynamic_Array
|
||||||
case Type_Info_Slice: return .Slice
|
case Type_Info_Slice: return .Slice
|
||||||
case Type_Info_Tuple: return .Tuple
|
case Type_Info_Parameters: return .Tuple
|
||||||
case Type_Info_Struct: return .Struct
|
case Type_Info_Struct: return .Struct
|
||||||
case Type_Info_Union: return .Union
|
case Type_Info_Union: return .Union
|
||||||
case Type_Info_Enum: return .Enum
|
case Type_Info_Enum: return .Enum
|
||||||
@@ -448,7 +449,14 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
struct_field_value :: proc(a: any, field: Struct_Field) -> any {
|
||||||
|
if a == nil { return nil }
|
||||||
|
return any {
|
||||||
|
rawptr(uintptr(a.data) + field.offset),
|
||||||
|
field.type.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
struct_field_names :: proc(T: typeid) -> []string {
|
struct_field_names :: proc(T: typeid) -> []string {
|
||||||
@@ -1438,7 +1446,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
|
|||||||
switch v in t.variant {
|
switch v in t.variant {
|
||||||
case Type_Info_Named:
|
case Type_Info_Named:
|
||||||
unreachable()
|
unreachable()
|
||||||
case Type_Info_Tuple:
|
case Type_Info_Parameters:
|
||||||
unreachable()
|
unreachable()
|
||||||
case Type_Info_Any:
|
case Type_Info_Any:
|
||||||
if !including_indirect_array_recursion {
|
if !including_indirect_array_recursion {
|
||||||
|
|||||||
+11
-5
@@ -101,8 +101,8 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool {
|
|||||||
y := b.variant.(Type_Info_Slice) or_return
|
y := b.variant.(Type_Info_Slice) or_return
|
||||||
return are_types_identical(x.elem, y.elem)
|
return are_types_identical(x.elem, y.elem)
|
||||||
|
|
||||||
case Type_Info_Tuple:
|
case Type_Info_Parameters:
|
||||||
y := b.variant.(Type_Info_Tuple) or_return
|
y := b.variant.(Type_Info_Parameters) or_return
|
||||||
if len(x.types) != len(y.types) { return false }
|
if len(x.types) != len(y.types) { return false }
|
||||||
for _, i in x.types {
|
for _, i in x.types {
|
||||||
xt, yt := x.types[i], y.types[i]
|
xt, yt := x.types[i], y.types[i]
|
||||||
@@ -335,9 +335,15 @@ is_slice :: proc(info: ^Type_Info) -> bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@(require_results)
|
@(require_results)
|
||||||
|
is_parameters :: proc(info: ^Type_Info) -> bool {
|
||||||
|
if info == nil { return false }
|
||||||
|
_, ok := type_info_base(info).variant.(Type_Info_Parameters)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
@(require_results, deprecated="prefer is_parameters")
|
||||||
is_tuple :: proc(info: ^Type_Info) -> bool {
|
is_tuple :: proc(info: ^Type_Info) -> bool {
|
||||||
if info == nil { return false }
|
if info == nil { return false }
|
||||||
_, ok := type_info_base(info).variant.(Type_Info_Tuple)
|
_, ok := type_info_base(info).variant.(Type_Info_Parameters)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@(require_results)
|
@(require_results)
|
||||||
@@ -490,7 +496,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
|
|||||||
if info.params == nil {
|
if info.params == nil {
|
||||||
io.write_string(w, "()", &n) or_return
|
io.write_string(w, "()", &n) or_return
|
||||||
} else {
|
} else {
|
||||||
t := info.params.variant.(Type_Info_Tuple)
|
t := info.params.variant.(Type_Info_Parameters)
|
||||||
io.write_string(w, "(", &n) or_return
|
io.write_string(w, "(", &n) or_return
|
||||||
for t, i in t.types {
|
for t, i in t.types {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@@ -504,7 +510,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -
|
|||||||
io.write_string(w, " -> ", &n) or_return
|
io.write_string(w, " -> ", &n) or_return
|
||||||
write_type(w, info.results, &n) or_return
|
write_type(w, info.results, &n) or_return
|
||||||
}
|
}
|
||||||
case Type_Info_Tuple:
|
case Type_Info_Parameters:
|
||||||
count := len(info.names)
|
count := len(info.names)
|
||||||
if count != 1 {
|
if count != 1 {
|
||||||
io.write_string(w, "(", &n) or_return
|
io.write_string(w, "(", &n) or_return
|
||||||
|
|||||||
+11
-10
@@ -83,8 +83,8 @@ Type_Info_Multi_Pointer :: struct {
|
|||||||
elem: ^Type_Info,
|
elem: ^Type_Info,
|
||||||
}
|
}
|
||||||
Type_Info_Procedure :: struct {
|
Type_Info_Procedure :: struct {
|
||||||
params: ^Type_Info, // Type_Info_Tuple
|
params: ^Type_Info, // Type_Info_Parameters
|
||||||
results: ^Type_Info, // Type_Info_Tuple
|
results: ^Type_Info, // Type_Info_Parameters
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
convention: Calling_Convention,
|
convention: Calling_Convention,
|
||||||
}
|
}
|
||||||
@@ -104,10 +104,12 @@ Type_Info_Enumerated_Array :: struct {
|
|||||||
}
|
}
|
||||||
Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int}
|
Type_Info_Dynamic_Array :: struct {elem: ^Type_Info, elem_size: int}
|
||||||
Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int}
|
Type_Info_Slice :: struct {elem: ^Type_Info, elem_size: int}
|
||||||
Type_Info_Tuple :: struct { // Only used for procedures parameters and results
|
|
||||||
|
Type_Info_Parameters :: struct { // Only used for procedures parameters and results
|
||||||
types: []^Type_Info,
|
types: []^Type_Info,
|
||||||
names: []string,
|
names: []string,
|
||||||
}
|
}
|
||||||
|
Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
|
||||||
|
|
||||||
Type_Info_Struct :: struct {
|
Type_Info_Struct :: struct {
|
||||||
types: []^Type_Info,
|
types: []^Type_Info,
|
||||||
@@ -208,7 +210,7 @@ Type_Info :: struct {
|
|||||||
Type_Info_Enumerated_Array,
|
Type_Info_Enumerated_Array,
|
||||||
Type_Info_Dynamic_Array,
|
Type_Info_Dynamic_Array,
|
||||||
Type_Info_Slice,
|
Type_Info_Slice,
|
||||||
Type_Info_Tuple,
|
Type_Info_Parameters,
|
||||||
Type_Info_Struct,
|
Type_Info_Struct,
|
||||||
Type_Info_Union,
|
Type_Info_Union,
|
||||||
Type_Info_Enum,
|
Type_Info_Enum,
|
||||||
@@ -505,11 +507,8 @@ Odin_Endian_Type :: type_of(ODIN_ENDIAN)
|
|||||||
foreign {
|
foreign {
|
||||||
@(link_name="__$startup_runtime")
|
@(link_name="__$startup_runtime")
|
||||||
_startup_runtime :: proc "odin" () ---
|
_startup_runtime :: proc "odin" () ---
|
||||||
}
|
@(link_name="__$cleanup_runtime")
|
||||||
|
_cleanup_runtime :: proc "odin" () ---
|
||||||
@(link_name="__$cleanup_runtime")
|
|
||||||
_cleanup_runtime :: proc() {
|
|
||||||
default_temp_allocator_destroy(&global_default_temp_allocator_data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_cleanup_runtime_contextless :: proc "contextless" () {
|
_cleanup_runtime_contextless :: proc "contextless" () {
|
||||||
@@ -622,7 +621,9 @@ __init_context :: proc "contextless" (c: ^Context) {
|
|||||||
c.allocator.data = nil
|
c.allocator.data = nil
|
||||||
|
|
||||||
c.temp_allocator.procedure = default_temp_allocator_proc
|
c.temp_allocator.procedure = default_temp_allocator_proc
|
||||||
c.temp_allocator.data = &global_default_temp_allocator_data
|
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||||
|
c.temp_allocator.data = &global_default_temp_allocator_data
|
||||||
|
}
|
||||||
|
|
||||||
when !ODIN_DISABLE_ASSERT {
|
when !ODIN_DISABLE_ASSERT {
|
||||||
c.assertion_failure_proc = default_assertion_failure_proc
|
c.assertion_failure_proc = default_assertion_failure_proc
|
||||||
|
|||||||
@@ -15,11 +15,15 @@ container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: type
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@thread_local global_default_temp_allocator_data: Default_Temp_Allocator
|
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||||
|
@thread_local global_default_temp_allocator_data: Default_Temp_Allocator
|
||||||
|
}
|
||||||
|
|
||||||
@builtin
|
@(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR)
|
||||||
init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
|
init_global_temporary_allocator :: proc(size: int, backup_allocator := context.allocator) {
|
||||||
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
|
when !NO_DEFAULT_TEMP_ALLOCATOR {
|
||||||
|
default_temp_allocator_init(&global_default_temp_allocator_data, size, backup_allocator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -231,13 +235,12 @@ make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
@(builtin)
|
@(builtin)
|
||||||
make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
|
make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1<<MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error {
|
||||||
make_map_expr_error_loc(loc, capacity)
|
make_map_expr_error_loc(loc, capacity)
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
m: T
|
err = reserve_map(&m, capacity, loc)
|
||||||
reserve_map(&m, capacity, loc)
|
return
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
@(builtin)
|
@(builtin)
|
||||||
make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error {
|
make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) #optional_allocator_error {
|
||||||
@@ -276,10 +279,8 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@builtin
|
@builtin
|
||||||
reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) {
|
reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error {
|
||||||
if m != nil {
|
return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil
|
||||||
__dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import "core:intrinsics"
|
||||||
|
|
||||||
|
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
|
||||||
|
|
||||||
|
Memory_Block :: struct {
|
||||||
|
prev: ^Memory_Block,
|
||||||
|
allocator: Allocator,
|
||||||
|
base: [^]byte,
|
||||||
|
used: uint,
|
||||||
|
capacity: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
Arena :: struct {
|
||||||
|
backing_allocator: Allocator,
|
||||||
|
curr_block: ^Memory_Block,
|
||||||
|
total_used: uint,
|
||||||
|
total_capacity: uint,
|
||||||
|
minimum_block_size: uint,
|
||||||
|
temp_count: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private, require_results)
|
||||||
|
safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
|
||||||
|
z, did_overflow := intrinsics.overflow_add(x, y)
|
||||||
|
return z, !did_overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) {
|
||||||
|
total_size := uint(capacity + size_of(Memory_Block))
|
||||||
|
base_offset := uintptr(size_of(Memory_Block))
|
||||||
|
|
||||||
|
min_alignment: int = max(16, align_of(Memory_Block))
|
||||||
|
data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return
|
||||||
|
block = (^Memory_Block)(raw_data(data))
|
||||||
|
end := uintptr(raw_data(data)[len(data):])
|
||||||
|
|
||||||
|
block.allocator = allocator
|
||||||
|
block.base = ([^]byte)(uintptr(block) + base_offset)
|
||||||
|
block.capacity = uint(end - uintptr(block.base))
|
||||||
|
|
||||||
|
// Should be zeroed
|
||||||
|
assert(block.used == 0)
|
||||||
|
assert(block.prev == nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
|
||||||
|
if block_to_free != nil {
|
||||||
|
allocator := block_to_free.allocator
|
||||||
|
mem_free(block_to_free, allocator, loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
|
||||||
|
alignment_offset := uint(0)
|
||||||
|
ptr := uintptr(block.base[block.used:])
|
||||||
|
mask := alignment-1
|
||||||
|
if ptr & mask != 0 {
|
||||||
|
alignment_offset = uint(alignment - (ptr & mask))
|
||||||
|
}
|
||||||
|
return alignment_offset
|
||||||
|
|
||||||
|
}
|
||||||
|
if block == nil {
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
alignment_offset := calc_alignment_offset(block, uintptr(alignment))
|
||||||
|
size, size_ok := safe_add(min_size, alignment_offset)
|
||||||
|
if !size_ok {
|
||||||
|
err = .Out_Of_Memory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if to_be_used, ok := safe_add(block.used, size); !ok || to_be_used > block.capacity {
|
||||||
|
err = .Out_Of_Memory
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data = block.base[block.used+alignment_offset:][:min_size]
|
||||||
|
block.used += size
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
|
||||||
|
p := ptr
|
||||||
|
modulo := p & (align-1)
|
||||||
|
if modulo != 0 {
|
||||||
|
p += align - modulo
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
||||||
|
|
||||||
|
size := size
|
||||||
|
if size == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity {
|
||||||
|
size = align_forward_uint(size, alignment)
|
||||||
|
if arena.minimum_block_size == 0 {
|
||||||
|
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
block_size := max(size, arena.minimum_block_size)
|
||||||
|
|
||||||
|
if arena.backing_allocator.procedure == nil {
|
||||||
|
arena.backing_allocator = default_allocator()
|
||||||
|
}
|
||||||
|
|
||||||
|
new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return
|
||||||
|
new_block.prev = arena.curr_block
|
||||||
|
arena.curr_block = new_block
|
||||||
|
arena.total_capacity += new_block.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_used := arena.curr_block.used
|
||||||
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
|
||||||
|
arena.total_used += arena.curr_block.used - prev_used
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// `arena_init` will initialize the arena with a usuable block.
|
||||||
|
// This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary
|
||||||
|
@(require_results)
|
||||||
|
arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error {
|
||||||
|
arena^ = {}
|
||||||
|
arena.backing_allocator = backing_allocator
|
||||||
|
arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB
|
||||||
|
new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return
|
||||||
|
arena.curr_block = new_block
|
||||||
|
arena.total_capacity += new_block.capacity
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
arena_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
|
if free_block := arena.curr_block; free_block != nil {
|
||||||
|
arena.curr_block = free_block.prev
|
||||||
|
|
||||||
|
arena.total_capacity -= free_block.capacity
|
||||||
|
memory_block_dealloc(free_block, loc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `arena_free_all` will free all but the first memory block, and then reset the memory block
|
||||||
|
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
|
for arena.curr_block != nil && arena.curr_block.prev != nil {
|
||||||
|
arena_free_last_memory_block(arena, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arena.curr_block != nil {
|
||||||
|
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
|
||||||
|
arena.curr_block.used = 0
|
||||||
|
}
|
||||||
|
arena.total_used = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
|
for arena.curr_block != nil {
|
||||||
|
free_block := arena.curr_block
|
||||||
|
arena.curr_block = free_block.prev
|
||||||
|
|
||||||
|
arena.total_capacity -= free_block.capacity
|
||||||
|
memory_block_dealloc(free_block, loc)
|
||||||
|
}
|
||||||
|
arena.total_used = 0
|
||||||
|
arena.total_capacity = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_allocator :: proc(arena: ^Arena) -> Allocator {
|
||||||
|
return Allocator{arena_allocator_proc, arena}
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||||
|
size, alignment: int,
|
||||||
|
old_memory: rawptr, old_size: int,
|
||||||
|
location := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
arena := (^Arena)(allocator_data)
|
||||||
|
|
||||||
|
size, alignment := uint(size), uint(alignment)
|
||||||
|
old_size := uint(old_size)
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case .Alloc, .Alloc_Non_Zeroed:
|
||||||
|
return arena_alloc(arena, size, alignment, location)
|
||||||
|
case .Free:
|
||||||
|
err = .Mode_Not_Implemented
|
||||||
|
case .Free_All:
|
||||||
|
arena_free_all(arena, location)
|
||||||
|
case .Resize:
|
||||||
|
old_data := ([^]byte)(old_memory)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case old_data == nil:
|
||||||
|
return arena_alloc(arena, size, alignment, location)
|
||||||
|
case size == old_size:
|
||||||
|
// return old memory
|
||||||
|
data = old_data[:size]
|
||||||
|
return
|
||||||
|
case size == 0:
|
||||||
|
err = .Mode_Not_Implemented
|
||||||
|
return
|
||||||
|
case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size:
|
||||||
|
// shrink data in-place
|
||||||
|
data = old_data[:size]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
new_memory := arena_alloc(arena, size, alignment, location) or_return
|
||||||
|
if new_memory == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copy(new_memory, old_data[:old_size])
|
||||||
|
return new_memory, nil
|
||||||
|
case .Query_Features:
|
||||||
|
set := (^Allocator_Mode_Set)(old_memory)
|
||||||
|
if set != nil {
|
||||||
|
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
|
||||||
|
}
|
||||||
|
case .Query_Info:
|
||||||
|
err = .Mode_Not_Implemented
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Arena_Temp :: struct {
|
||||||
|
arena: ^Arena,
|
||||||
|
block: ^Memory_Block,
|
||||||
|
used: uint,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
||||||
|
assert(arena != nil, "nil arena", loc)
|
||||||
|
|
||||||
|
temp.arena = arena
|
||||||
|
temp.block = arena.curr_block
|
||||||
|
if arena.curr_block != nil {
|
||||||
|
temp.used = arena.curr_block.used
|
||||||
|
}
|
||||||
|
arena.temp_count += 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
|
if temp.arena == nil {
|
||||||
|
assert(temp.block == nil)
|
||||||
|
assert(temp.used == 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arena := temp.arena
|
||||||
|
|
||||||
|
if temp.block != nil {
|
||||||
|
memory_block_found := false
|
||||||
|
for block := arena.curr_block; block != nil; block = block.prev {
|
||||||
|
if block == temp.block {
|
||||||
|
memory_block_found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !memory_block_found {
|
||||||
|
assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
for arena.curr_block != temp.block {
|
||||||
|
arena_free_last_memory_block(arena)
|
||||||
|
}
|
||||||
|
|
||||||
|
if block := arena.curr_block; block != nil {
|
||||||
|
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
|
||||||
|
amount_to_zero := min(block.used-temp.used, block.capacity-block.used)
|
||||||
|
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
|
||||||
|
block.used = temp.used
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
|
||||||
|
arena.temp_count -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the use of a `arena_temp_begin` entirely
|
||||||
|
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
|
assert(temp.arena != nil, "nil arena", loc)
|
||||||
|
arena := temp.arena
|
||||||
|
|
||||||
|
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
|
||||||
|
arena.temp_count -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
||||||
|
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
||||||
|
}
|
||||||
@@ -1,159 +1,38 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte)
|
||||||
|
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
||||||
|
|
||||||
|
when NO_DEFAULT_TEMP_ALLOCATOR {
|
||||||
when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR {
|
|
||||||
Default_Temp_Allocator :: struct {}
|
Default_Temp_Allocator :: struct {}
|
||||||
|
|
||||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) {}
|
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
|
||||||
|
|
||||||
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {}
|
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {}
|
||||||
|
|
||||||
default_temp_allocator_proc :: nil_allocator_proc
|
default_temp_allocator_proc :: nil_allocator_proc
|
||||||
} else {
|
|
||||||
Default_Temp_Allocator :: struct {
|
@(require_results)
|
||||||
data: []byte,
|
default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
|
||||||
curr_offset: int,
|
return
|
||||||
prev_allocation: rawptr,
|
|
||||||
backup_allocator: Allocator,
|
|
||||||
leaked_allocations: [dynamic][]byte,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backup_allocator := context.allocator) {
|
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator)
|
}
|
||||||
s.curr_offset = 0
|
} else {
|
||||||
s.prev_allocation = nil
|
Default_Temp_Allocator :: struct {
|
||||||
s.backup_allocator = backup_allocator
|
arena: Arena,
|
||||||
s.leaked_allocations.allocator = backup_allocator
|
}
|
||||||
|
|
||||||
|
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
|
||||||
|
_ = arena_init(&s.arena, uint(size), backing_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
|
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
|
||||||
if s == nil {
|
if s != nil {
|
||||||
return
|
arena_destroy(&s.arena)
|
||||||
|
s^ = {}
|
||||||
}
|
}
|
||||||
for ptr in s.leaked_allocations {
|
|
||||||
free(raw_data(ptr), s.backup_allocator)
|
|
||||||
}
|
|
||||||
delete(s.leaked_allocations)
|
|
||||||
delete(s.data, s.backup_allocator)
|
|
||||||
s^ = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
default_temp_allocator_alloc :: proc(s: ^Default_Temp_Allocator, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
|
||||||
size := size
|
|
||||||
size = align_forward_int(size, alignment)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case s.curr_offset+size <= len(s.data):
|
|
||||||
start := uintptr(raw_data(s.data))
|
|
||||||
ptr := start + uintptr(s.curr_offset)
|
|
||||||
ptr = align_forward_uintptr(ptr, uintptr(alignment))
|
|
||||||
mem_zero(rawptr(ptr), size)
|
|
||||||
|
|
||||||
s.prev_allocation = rawptr(ptr)
|
|
||||||
offset := int(ptr - start)
|
|
||||||
s.curr_offset = offset + size
|
|
||||||
return byte_slice(rawptr(ptr), size), .None
|
|
||||||
|
|
||||||
case size <= len(s.data):
|
|
||||||
start := uintptr(raw_data(s.data))
|
|
||||||
ptr := align_forward_uintptr(start, uintptr(alignment))
|
|
||||||
mem_zero(rawptr(ptr), size)
|
|
||||||
|
|
||||||
s.prev_allocation = rawptr(ptr)
|
|
||||||
offset := int(ptr - start)
|
|
||||||
s.curr_offset = offset + size
|
|
||||||
return byte_slice(rawptr(ptr), size), .None
|
|
||||||
}
|
|
||||||
a := s.backup_allocator
|
|
||||||
if a.procedure == nil {
|
|
||||||
a = context.allocator
|
|
||||||
s.backup_allocator = a
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := mem_alloc_bytes(size, alignment, a, loc)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
if s.leaked_allocations == nil {
|
|
||||||
s.leaked_allocations = make([dynamic][]byte, a)
|
|
||||||
}
|
|
||||||
append(&s.leaked_allocations, data)
|
|
||||||
|
|
||||||
// TODO(bill): Should leaks be notified about?
|
|
||||||
if logger := context.logger; logger.lowest_level <= .Warning {
|
|
||||||
if logger.procedure != nil {
|
|
||||||
logger.procedure(logger.data, .Warning, "default temp allocator resorted to backup_allocator" , logger.options, loc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, .None
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
default_temp_allocator_free :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, loc := #caller_location) -> Allocator_Error {
|
|
||||||
if old_memory == nil {
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
|
|
||||||
start := uintptr(raw_data(s.data))
|
|
||||||
end := start + uintptr(len(s.data))
|
|
||||||
old_ptr := uintptr(old_memory)
|
|
||||||
|
|
||||||
if s.prev_allocation == old_memory {
|
|
||||||
s.curr_offset = int(uintptr(s.prev_allocation) - start)
|
|
||||||
s.prev_allocation = nil
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
|
|
||||||
if start <= old_ptr && old_ptr < end {
|
|
||||||
// NOTE(bill): Cannot free this pointer but it is valid
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s.leaked_allocations) != 0 {
|
|
||||||
for data, i in s.leaked_allocations {
|
|
||||||
ptr := raw_data(data)
|
|
||||||
if ptr == old_memory {
|
|
||||||
free(ptr, s.backup_allocator)
|
|
||||||
ordered_remove(&s.leaked_allocations, i)
|
|
||||||
return .None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .Invalid_Pointer
|
|
||||||
// panic("invalid pointer passed to default_temp_allocator");
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
default_temp_allocator_free_all :: proc(s: ^Default_Temp_Allocator, loc := #caller_location) {
|
|
||||||
s.curr_offset = 0
|
|
||||||
s.prev_allocation = nil
|
|
||||||
for data in s.leaked_allocations {
|
|
||||||
free(raw_data(data), s.backup_allocator)
|
|
||||||
}
|
|
||||||
clear(&s.leaked_allocations)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
default_temp_allocator_resize :: proc(s: ^Default_Temp_Allocator, old_memory: rawptr, old_size, size, alignment: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
|
||||||
begin := uintptr(raw_data(s.data))
|
|
||||||
end := begin + uintptr(len(s.data))
|
|
||||||
old_ptr := uintptr(old_memory)
|
|
||||||
if old_memory == s.prev_allocation && old_ptr & uintptr(alignment)-1 == 0 {
|
|
||||||
if old_ptr+uintptr(size) < end {
|
|
||||||
s.curr_offset = int(old_ptr-begin)+size
|
|
||||||
return byte_slice(old_memory, size), .None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data, err := default_temp_allocator_alloc(s, size, alignment, loc)
|
|
||||||
if err == .None {
|
|
||||||
copy(data, byte_slice(old_memory, old_size))
|
|
||||||
err = default_temp_allocator_free(s, old_memory, loc)
|
|
||||||
}
|
|
||||||
return data, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
default_temp_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||||
@@ -161,40 +40,40 @@ when ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR
|
|||||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
|
||||||
s := (^Default_Temp_Allocator)(allocator_data)
|
s := (^Default_Temp_Allocator)(allocator_data)
|
||||||
|
return arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc)
|
||||||
|
}
|
||||||
|
|
||||||
if s.data == nil {
|
@(require_results)
|
||||||
default_temp_allocator_init(s, DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, default_allocator())
|
default_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: Arena_Temp) {
|
||||||
|
if context.temp_allocator.data == &global_default_temp_allocator_data {
|
||||||
|
temp = arena_temp_begin(&global_default_temp_allocator_data.arena, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case .Alloc, .Alloc_Non_Zeroed:
|
|
||||||
data, err = default_temp_allocator_alloc(s, size, alignment, loc)
|
|
||||||
case .Free:
|
|
||||||
err = default_temp_allocator_free(s, old_memory, loc)
|
|
||||||
|
|
||||||
case .Free_All:
|
|
||||||
default_temp_allocator_free_all(s, loc)
|
|
||||||
|
|
||||||
case .Resize:
|
|
||||||
data, err = default_temp_allocator_resize(s, old_memory, old_size, size, alignment, loc)
|
|
||||||
|
|
||||||
case .Query_Features:
|
|
||||||
set := (^Allocator_Mode_Set)(old_memory)
|
|
||||||
if set != nil {
|
|
||||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
|
|
||||||
}
|
|
||||||
|
|
||||||
case .Query_Info:
|
|
||||||
return nil, .Mode_Not_Implemented
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||||
|
arena_temp_end(temp, loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(fini, private)
|
||||||
|
_destroy_temp_allocator_fini :: proc() {
|
||||||
|
default_temp_allocator_destroy(&global_default_temp_allocator_data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(deferred_out=default_temp_allocator_temp_end)
|
||||||
|
DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (Arena_Temp, Source_Code_Location) {
|
||||||
|
if ignore {
|
||||||
|
return {}, loc
|
||||||
|
} else {
|
||||||
|
return default_temp_allocator_temp_begin(loc), loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator {
|
default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator {
|
||||||
return Allocator{
|
return Allocator{
|
||||||
procedure = default_temp_allocator_proc,
|
procedure = default_temp_allocator_proc,
|
||||||
data = allocator,
|
data = allocator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-10
@@ -184,32 +184,33 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||||
if allocator.procedure == nil {
|
if allocator.procedure == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if new_size == 0 {
|
if new_size == 0 {
|
||||||
if ptr != nil {
|
if ptr != nil {
|
||||||
_, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
|
_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
return nil, nil
|
return
|
||||||
} else if ptr == nil {
|
} else if ptr == nil {
|
||||||
return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
|
return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
|
||||||
} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
|
} else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 {
|
||||||
return ([^]byte)(ptr)[:old_size], nil
|
data = ([^]byte)(ptr)[:old_size]
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
|
data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc)
|
||||||
if err == .Mode_Not_Implemented {
|
if err == .Mode_Not_Implemented {
|
||||||
data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
|
data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, err
|
return
|
||||||
}
|
}
|
||||||
copy(data, ([^]byte)(ptr)[:old_size])
|
copy(data, ([^]byte)(ptr)[:old_size])
|
||||||
_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
|
_, err = allocator.procedure(allocator.data, .Free, 0, 0, ptr, old_size, loc)
|
||||||
}
|
}
|
||||||
return data, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
|
memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
|
||||||
@@ -223,7 +224,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
|
|||||||
|
|
||||||
when size_of(uint) == 8 {
|
when size_of(uint) == 8 {
|
||||||
if word_length := length >> 3; word_length != 0 {
|
if word_length := length >> 3; word_length != 0 {
|
||||||
for i in 0..<word_length {
|
for _ in 0..<word_length {
|
||||||
if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
|
if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -254,7 +255,7 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
if word_length := length >> 2; word_length != 0 {
|
if word_length := length >> 2; word_length != 0 {
|
||||||
for i in 0..<word_length {
|
for _ in 0..<word_length {
|
||||||
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
|
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-23
@@ -2,8 +2,11 @@ package runtime
|
|||||||
|
|
||||||
_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
|
_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
@(private="file")
|
||||||
|
_INTEGER_DIGITS_VAR := _INTEGER_DIGITS
|
||||||
|
|
||||||
when !ODIN_DISALLOW_RTTI {
|
when !ODIN_DISALLOW_RTTI {
|
||||||
print_any_single :: proc(arg: any) {
|
print_any_single :: proc "contextless" (arg: any) {
|
||||||
x := arg
|
x := arg
|
||||||
if loc, ok := x.(Source_Code_Location); ok {
|
if loc, ok := x.(Source_Code_Location); ok {
|
||||||
print_caller_location(loc)
|
print_caller_location(loc)
|
||||||
@@ -46,6 +49,12 @@ when !ODIN_DISALLOW_RTTI {
|
|||||||
case uint: print_uint(v)
|
case uint: print_uint(v)
|
||||||
case uintptr: print_uintptr(v)
|
case uintptr: print_uintptr(v)
|
||||||
|
|
||||||
|
case bool: print_string("true" if v else "false")
|
||||||
|
case b8: print_string("true" if v else "false")
|
||||||
|
case b16: print_string("true" if v else "false")
|
||||||
|
case b32: print_string("true" if v else "false")
|
||||||
|
case b64: print_string("true" if v else "false")
|
||||||
|
|
||||||
case:
|
case:
|
||||||
ti := type_info_of(x.id)
|
ti := type_info_of(x.id)
|
||||||
#partial switch v in ti.variant {
|
#partial switch v in ti.variant {
|
||||||
@@ -57,7 +66,7 @@ when !ODIN_DISALLOW_RTTI {
|
|||||||
print_string("<invalid-value>")
|
print_string("<invalid-value>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println_any :: proc(args: ..any) {
|
println_any :: proc "contextless" (args: ..any) {
|
||||||
loop: for arg, i in args {
|
loop: for arg, i in args {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
print_string(" ")
|
print_string(" ")
|
||||||
@@ -105,14 +114,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) {
|
|||||||
return buf, 4
|
return buf, 4
|
||||||
}
|
}
|
||||||
|
|
||||||
print_string :: proc "contextless" (str: string) -> (int, _OS_Errno) {
|
print_string :: proc "contextless" (str: string) -> (n: int) {
|
||||||
return os_write(transmute([]byte)str)
|
n, _ = os_write(transmute([]byte)str)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno) {
|
print_strings :: proc "contextless" (args: ..string) -> (n: int) {
|
||||||
for str in args {
|
for str in args {
|
||||||
m: int
|
m, err := os_write(transmute([]byte)str)
|
||||||
m, err = os_write(transmute([]byte)str)
|
|
||||||
n += m
|
n += m
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
break
|
break
|
||||||
@@ -121,8 +130,9 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int, err: _OS_Errno)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print_byte :: proc "contextless" (b: byte) -> (int, _OS_Errno) {
|
print_byte :: proc "contextless" (b: byte) -> (n: int) {
|
||||||
return os_write([]byte{b})
|
n, _ = os_write([]byte{b})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print_encoded_rune :: proc "contextless" (r: rune) {
|
print_encoded_rune :: proc "contextless" (r: rune) {
|
||||||
@@ -141,11 +151,10 @@ print_encoded_rune :: proc "contextless" (r: rune) {
|
|||||||
if r <= 0 {
|
if r <= 0 {
|
||||||
print_string("\\x00")
|
print_string("\\x00")
|
||||||
} else if r < 32 {
|
} else if r < 32 {
|
||||||
digits := _INTEGER_DIGITS
|
|
||||||
n0, n1 := u8(r) >> 4, u8(r) & 0xf
|
n0, n1 := u8(r) >> 4, u8(r) & 0xf
|
||||||
print_string("\\x")
|
print_string("\\x")
|
||||||
print_byte(digits[n0])
|
print_byte(_INTEGER_DIGITS_VAR[n0])
|
||||||
print_byte(digits[n1])
|
print_byte(_INTEGER_DIGITS_VAR[n1])
|
||||||
} else {
|
} else {
|
||||||
print_rune(r)
|
print_rune(r)
|
||||||
}
|
}
|
||||||
@@ -153,7 +162,7 @@ print_encoded_rune :: proc "contextless" (r: rune) {
|
|||||||
print_byte('\'')
|
print_byte('\'')
|
||||||
}
|
}
|
||||||
|
|
||||||
print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check {
|
print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check {
|
||||||
RUNE_SELF :: 0x80
|
RUNE_SELF :: 0x80
|
||||||
|
|
||||||
if r < RUNE_SELF {
|
if r < RUNE_SELF {
|
||||||
@@ -161,29 +170,27 @@ print_rune :: proc "contextless" (r: rune) -> (int, _OS_Errno) #no_bounds_check
|
|||||||
}
|
}
|
||||||
|
|
||||||
b, n := encode_rune(r)
|
b, n := encode_rune(r)
|
||||||
return os_write(b[:n])
|
m, _ := os_write(b[:n])
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
|
print_u64 :: proc "contextless" (x: u64) #no_bounds_check {
|
||||||
digits := _INTEGER_DIGITS
|
|
||||||
|
|
||||||
a: [129]byte
|
a: [129]byte
|
||||||
i := len(a)
|
i := len(a)
|
||||||
b := u64(10)
|
b := u64(10)
|
||||||
u := x
|
u := x
|
||||||
for u >= b {
|
for u >= b {
|
||||||
i -= 1; a[i] = digits[u % b]
|
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
|
||||||
u /= b
|
u /= b
|
||||||
}
|
}
|
||||||
i -= 1; a[i] = digits[u % b]
|
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
|
||||||
|
|
||||||
os_write(a[i:])
|
os_write(a[i:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
|
print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
|
||||||
digits := _INTEGER_DIGITS
|
|
||||||
b :: i64(10)
|
b :: i64(10)
|
||||||
|
|
||||||
u := x
|
u := x
|
||||||
@@ -193,10 +200,10 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check {
|
|||||||
a: [129]byte
|
a: [129]byte
|
||||||
i := len(a)
|
i := len(a)
|
||||||
for u >= b {
|
for u >= b {
|
||||||
i -= 1; a[i] = digits[u % b]
|
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
|
||||||
u /= b
|
u /= b
|
||||||
}
|
}
|
||||||
i -= 1; a[i] = digits[u % b]
|
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
|
||||||
if neg {
|
if neg {
|
||||||
i -= 1; a[i] = '-'
|
i -= 1; a[i] = '-'
|
||||||
}
|
}
|
||||||
@@ -303,7 +310,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
|
|||||||
if info.params == nil {
|
if info.params == nil {
|
||||||
print_string("()")
|
print_string("()")
|
||||||
} else {
|
} else {
|
||||||
t := info.params.variant.(Type_Info_Tuple)
|
t := info.params.variant.(Type_Info_Parameters)
|
||||||
print_byte('(')
|
print_byte('(')
|
||||||
for t, i in t.types {
|
for t, i in t.types {
|
||||||
if i > 0 { print_string(", ") }
|
if i > 0 { print_string(", ") }
|
||||||
@@ -315,7 +322,7 @@ print_type :: proc "contextless" (ti: ^Type_Info) {
|
|||||||
print_string(" -> ")
|
print_string(" -> ")
|
||||||
print_type(info.results)
|
print_type(info.results)
|
||||||
}
|
}
|
||||||
case Type_Info_Tuple:
|
case Type_Info_Parameters:
|
||||||
count := len(info.names)
|
count := len(info.names)
|
||||||
if count != 1 { print_byte('(') }
|
if count != 1 { print_byte('(') }
|
||||||
for name, i in info.names {
|
for name, i in info.names {
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ reverse_sort :: proc(data: $T/[]$E) where ORD(E) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E) {
|
reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) {
|
||||||
context._internal = rawptr(less)
|
context._internal = rawptr(less)
|
||||||
sort_by(data, proc(i, j: E) -> bool {
|
sort_by(data, proc(i, j: E) -> bool {
|
||||||
k := (proc(i, j: E) -> bool)(context._internal)
|
k := (proc(i, j: E) -> bool)(context._internal)
|
||||||
@@ -189,7 +189,7 @@ reverse_sort_by :: proc(data: $T/[]$E, less: proc(i, j: E) -> bool) where ORD(E)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) where ORD(E) {
|
reverse_sort_by_cmp :: proc(data: $T/[]$E, cmp: proc(i, j: E) -> Ordering) {
|
||||||
context._internal = rawptr(cmp)
|
context._internal = rawptr(cmp)
|
||||||
sort_by_cmp(data, proc(i, j: E) -> Ordering {
|
sort_by_cmp(data, proc(i, j: E) -> Ordering {
|
||||||
k := (proc(i, j: E) -> Ordering)(context._internal)
|
k := (proc(i, j: E) -> Ordering)(context._internal)
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package sort
|
|
||||||
|
|
||||||
import "core:intrinsics"
|
|
||||||
import "core:runtime"
|
|
||||||
import "core:slice"
|
|
||||||
|
|
||||||
_ :: runtime
|
|
||||||
_ :: slice
|
|
||||||
|
|
||||||
map_entries_by_key :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(K) {
|
|
||||||
Entry :: struct {
|
|
||||||
hash: uintptr,
|
|
||||||
next: int,
|
|
||||||
key: K,
|
|
||||||
value: V,
|
|
||||||
}
|
|
||||||
|
|
||||||
header := runtime.__get_map_header(m)
|
|
||||||
entries := (^[dynamic]Entry)(&header.m.entries)
|
|
||||||
slice.sort_by_key(entries[:], proc(e: Entry) -> K { return e.key })
|
|
||||||
runtime.__dynamic_map_reset_entries(header, loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
map_entries_by_value :: proc(m: ^$M/map[$K]$V, loc := #caller_location) where intrinsics.type_is_ordered(V) {
|
|
||||||
Entry :: struct {
|
|
||||||
hash: uintptr,
|
|
||||||
next: int,
|
|
||||||
key: K,
|
|
||||||
value: V,
|
|
||||||
}
|
|
||||||
|
|
||||||
header := runtime.__get_map_header(m)
|
|
||||||
entries := (^[dynamic]Entry)(&header.m.entries)
|
|
||||||
slice.sort_by_key(entries[:], proc(e: Entry) -> V { return e.value })
|
|
||||||
runtime.__dynamic_map_reset_entries(header, loc)
|
|
||||||
}
|
|
||||||
+44
-15
@@ -556,19 +556,51 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) {
|
|||||||
return f32(v), ok
|
return f32(v), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
||||||
|
nr: int
|
||||||
|
value, nr, ok = parse_f64_prefix(str)
|
||||||
|
if ok && len(str) != nr {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
if n != nil { n^ = nr }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parses a 32-bit floating point number from a string.
|
||||||
|
//
|
||||||
|
// Returns ok=false if a base 10 float could not be found,
|
||||||
|
// or if the input string contained more than just the number.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// n, _, ok := strconv.parse_f32("12.34eee");
|
||||||
|
// assert(n == 12.34 && ok);
|
||||||
|
//
|
||||||
|
// n, _, ok = strconv.parse_f32("12.34");
|
||||||
|
// assert(n == 12.34 && ok);
|
||||||
|
// ```
|
||||||
|
parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) {
|
||||||
|
f: f64
|
||||||
|
f, nr, ok = parse_f64_prefix(str)
|
||||||
|
value = f32(f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Parses a 64-bit floating point number from a string.
|
// Parses a 64-bit floating point number from a string.
|
||||||
//
|
//
|
||||||
// Returns ok=false if a base 10 float could not be found,
|
// Returns ok=false if a base 10 float could not be found,
|
||||||
// or if the input string contained more than just the number.
|
// or if the input string contained more than just the number.
|
||||||
//
|
//
|
||||||
// ```
|
// ```
|
||||||
// n, ok := strconv.parse_f32("12.34eee");
|
// n, _, ok := strconv.parse_f32("12.34eee");
|
||||||
// assert(n == 12.34 && ok);
|
// assert(n == 12.34 && ok);
|
||||||
//
|
//
|
||||||
// n, ok = strconv.parse_f32("12.34");
|
// n, _, ok = strconv.parse_f32("12.34");
|
||||||
// assert(n == 12.34 && ok);
|
// assert(n == 12.34 && ok);
|
||||||
// ```
|
// ```
|
||||||
parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
|
||||||
common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
|
common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int {
|
||||||
n := len(prefix)
|
n := len(prefix)
|
||||||
if n > len(s) {
|
if n > len(s) {
|
||||||
@@ -678,8 +710,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
saw_digits = true
|
saw_digits = true
|
||||||
nd += 1
|
nd += 1
|
||||||
if nd_mant < MAX_MANT_DIGITS {
|
if nd_mant < MAX_MANT_DIGITS {
|
||||||
MAX_MANT_DIGITS *= 16
|
mantissa *= 16
|
||||||
MAX_MANT_DIGITS += int(lower(c) - 'a' + 10)
|
mantissa += u64(lower(c) - 'a' + 10)
|
||||||
nd_mant += 1
|
nd_mant += 1
|
||||||
} else {
|
} else {
|
||||||
trunc = true
|
trunc = true
|
||||||
@@ -729,12 +761,11 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
if mantissa != 0 {
|
if mantissa != 0 {
|
||||||
exp = decimal_point - nd_mant
|
exp = decimal_point - nd_mant
|
||||||
}
|
}
|
||||||
// TODO(bill): check underscore correctness
|
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_hex :: proc(s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
|
parse_hex :: proc "contextless" (s: string, mantissa: u64, exp: int, neg, trunc: bool) -> (f64, bool) {
|
||||||
info := &_f64_info
|
info := &_f64_info
|
||||||
|
|
||||||
mantissa, exp := mantissa, exp
|
mantissa, exp := mantissa, exp
|
||||||
@@ -751,7 +782,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
mantissa |= 1
|
mantissa |= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for mantissa >> (info.mantbits+2) == 0 {
|
for mantissa != 0 && mantissa >> (info.mantbits+2) == 0 {
|
||||||
mantissa = mantissa>>1 | mantissa&1
|
mantissa = mantissa>>1 | mantissa&1
|
||||||
exp += 1
|
exp += 1
|
||||||
}
|
}
|
||||||
@@ -795,9 +826,6 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nr: int
|
|
||||||
defer if n != nil { n^ = nr }
|
|
||||||
|
|
||||||
if value, nr, ok = check_special(str); ok {
|
if value, nr, ok = check_special(str); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -808,7 +836,8 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
mantissa, exp, neg, trunc, hex, nr = parse_components(str) or_return
|
mantissa, exp, neg, trunc, hex, nr = parse_components(str) or_return
|
||||||
|
|
||||||
if hex {
|
if hex {
|
||||||
return parse_hex(str, mantissa, exp, neg, trunc)
|
value, ok = parse_hex(str, mantissa, exp, neg, trunc)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trunc_block: if !trunc {
|
trunc_block: if !trunc {
|
||||||
@@ -827,7 +856,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case exp == 0:
|
case exp == 0:
|
||||||
return f, true
|
return f, nr, true
|
||||||
case exp > 0 && exp <= 15+22:
|
case exp > 0 && exp <= 15+22:
|
||||||
if exp > 22 {
|
if exp > 22 {
|
||||||
f *= pow10[exp-22]
|
f *= pow10[exp-22]
|
||||||
@@ -836,9 +865,9 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) {
|
|||||||
if f > 1e15 || f < 1e-15 {
|
if f > 1e15 || f < 1e-15 {
|
||||||
break trunc_block
|
break trunc_block
|
||||||
}
|
}
|
||||||
return f * pow10[exp], true
|
return f * pow10[exp], nr, true
|
||||||
case -22 <= exp && exp < 0:
|
case -22 <= exp && exp < 0:
|
||||||
return f / pow10[-exp], true
|
return f / pow10[-exp], nr, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d: decimal.Decimal
|
d: decimal.Decimal
|
||||||
|
|||||||
@@ -3,9 +3,22 @@ package strings
|
|||||||
|
|
||||||
import "core:unicode/utf8"
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ascii_Set is designed to store ASCII characters efficiently as a bit-array
|
||||||
|
Each bit in the array corresponds to a specific ASCII character, where the value of the bit (0 or 1)
|
||||||
|
indicates if the character is present in the set or not.
|
||||||
|
*/
|
||||||
Ascii_Set :: distinct [8]u32
|
Ascii_Set :: distinct [8]u32
|
||||||
|
/*
|
||||||
|
Creates an Ascii_Set with unique characters from the input string.
|
||||||
|
|
||||||
// create an ascii set of all unique characters in the string
|
Inputs:
|
||||||
|
- chars: A string containing characters to include in the Ascii_Set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- as: An Ascii_Set with unique characters from the input string.
|
||||||
|
- ok: false if any character in the input string is not a valid ASCII character.
|
||||||
|
*/
|
||||||
ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_check {
|
ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_check {
|
||||||
for i in 0..<len(chars) {
|
for i in 0..<len(chars) {
|
||||||
c := chars[i]
|
c := chars[i]
|
||||||
@@ -17,8 +30,16 @@ ascii_set_make :: proc(chars: string) -> (as: Ascii_Set, ok: bool) #no_bounds_ch
|
|||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Determines if a given char is contained within an Ascii_Set.
|
||||||
|
|
||||||
// returns true when the `c` byte is contained in the `as` ascii set
|
Inputs:
|
||||||
|
- as: The Ascii_Set to search.
|
||||||
|
- c: The char to check for in the Ascii_Set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A boolean indicating if the byte is contained in the Ascii_Set (true) or not (false).
|
||||||
|
*/
|
||||||
ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check {
|
ascii_set_contains :: proc(as: Ascii_Set, c: byte) -> bool #no_bounds_check {
|
||||||
return as[c>>5] & (1<<(c&31)) != 0
|
return as[c>>5] & (1<<(c&31)) != 0
|
||||||
}
|
}
|
||||||
+515
-100
@@ -4,70 +4,135 @@ import "core:runtime"
|
|||||||
import "core:unicode/utf8"
|
import "core:unicode/utf8"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
import "core:io"
|
import "core:io"
|
||||||
|
|
||||||
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
dynamic byte buffer / string builder with helper procedures
|
Type definition for a procedure that flushes a Builder
|
||||||
the dynamic array is wrapped inside the struct to be more opaque
|
|
||||||
you can use `fmt.sbprint*` procedures with a `^strings.Builder` directly
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A boolean indicating whether the Builder should be reset
|
||||||
|
*/
|
||||||
|
Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool)
|
||||||
|
/*
|
||||||
|
A dynamic byte buffer / string builder with helper procedures
|
||||||
|
The dynamic array is wrapped inside the struct to be more opaque
|
||||||
|
You can use `fmt.sbprint*` procedures with a `^strings.Builder` directly
|
||||||
*/
|
*/
|
||||||
Builder :: struct {
|
Builder :: struct {
|
||||||
buf: [dynamic]byte,
|
buf: [dynamic]byte,
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Produces a Builder with a default length of 0 and cap of 16
|
||||||
|
|
||||||
// return a builder, default length 0 / cap 16 are done through make
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Builder
|
||||||
|
*/
|
||||||
builder_make_none :: proc(allocator := context.allocator) -> Builder {
|
builder_make_none :: proc(allocator := context.allocator) -> Builder {
|
||||||
return Builder{buf=make([dynamic]byte, allocator)}
|
return Builder{buf=make([dynamic]byte, allocator)}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Produces a Builder with a specified length and cap of max(16,len) byte buffer
|
||||||
|
|
||||||
// return a builder, with a set length `len` and cap 16 byte buffer
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- len: The desired length of the Builder's buffer
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Builder
|
||||||
|
*/
|
||||||
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
|
builder_make_len :: proc(len: int, allocator := context.allocator) -> Builder {
|
||||||
return Builder{buf=make([dynamic]byte, len, allocator)}
|
return Builder{buf=make([dynamic]byte, len, allocator)}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Produces a Builder with a specified length and cap
|
||||||
|
|
||||||
// return a builder, with a set length `len` byte buffer and a custom `cap`
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- len: The desired length of the Builder's buffer
|
||||||
|
- cap: The desired capacity of the Builder's buffer, cap is max(cap, len)
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Builder
|
||||||
|
*/
|
||||||
builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
|
builder_make_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
|
||||||
return Builder{buf=make([dynamic]byte, len, cap, allocator)}
|
return Builder{buf=make([dynamic]byte, len, cap, allocator)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// overload simple `builder_make_*` with or without len / cap parameters
|
// overload simple `builder_make_*` with or without len / cap parameters
|
||||||
builder_make :: proc{
|
builder_make :: proc{
|
||||||
builder_make_none,
|
builder_make_none,
|
||||||
builder_make_len,
|
builder_make_len,
|
||||||
builder_make_len_cap,
|
builder_make_len_cap,
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a Builder with a length of 0 and cap of 16
|
||||||
|
It replaces the existing `buf`
|
||||||
|
|
||||||
// initialize a builder, default length 0 / cap 16 are done through make
|
*Allocates Using Provided Allocator*
|
||||||
// replaces the existing `buf`
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
initialized ^Builder
|
||||||
|
*/
|
||||||
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
|
builder_init_none :: proc(b: ^Builder, allocator := context.allocator) -> ^Builder {
|
||||||
b.buf = make([dynamic]byte, allocator)
|
b.buf = make([dynamic]byte, allocator)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a Builder with a specified length and cap, which is max(len,16)
|
||||||
|
It replaces the existing `buf`
|
||||||
|
|
||||||
// initialize a builder, with a set length `len` and cap 16 byte buffer
|
*Allocates Using Provided Allocator*
|
||||||
// replaces the existing `buf`
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- len: The desired length of the Builder's buffer
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Initialized ^Builder
|
||||||
|
*/
|
||||||
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
|
builder_init_len :: proc(b: ^Builder, len: int, allocator := context.allocator) -> ^Builder {
|
||||||
b.buf = make([dynamic]byte, len, allocator)
|
b.buf = make([dynamic]byte, len, allocator)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a Builder with a specified length and cap
|
||||||
|
It replaces the existing `buf`
|
||||||
|
|
||||||
// initialize a builder, with a set length `len` byte buffer and a custom `cap`
|
Inputs:
|
||||||
// replaces the existing `buf`
|
- b: A pointer to the Builder
|
||||||
|
- len: The desired length of the Builder's buffer
|
||||||
|
- cap: The desired capacity of the Builder's buffer, actual max(len,cap)
|
||||||
|
- allocator: (default is context.allocator)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A pointer to the initialized Builder
|
||||||
|
*/
|
||||||
builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder {
|
builder_init_len_cap :: proc(b: ^Builder, len, cap: int, allocator := context.allocator) -> ^Builder {
|
||||||
b.buf = make([dynamic]byte, len, cap, allocator)
|
b.buf = make([dynamic]byte, len, cap, allocator)
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
// Overload simple `builder_init_*` with or without len / ap parameters
|
||||||
// overload simple `builder_init_*` with or without len / ap parameters
|
|
||||||
builder_init :: proc{
|
builder_init :: proc{
|
||||||
builder_init_none,
|
builder_init_none,
|
||||||
builder_init_len,
|
builder_init_len,
|
||||||
builder_init_len_cap,
|
builder_init_len_cap,
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
_builder_stream_vtable := io.Stream_VTable{
|
_builder_stream_vtable_obj := io.Stream_VTable{
|
||||||
impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
|
impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
|
||||||
b := (^Builder)(s.stream_data)
|
b := (^Builder)(s.stream_data)
|
||||||
n = write_bytes(b, p)
|
n = write_bytes(b, p)
|
||||||
@@ -90,46 +155,95 @@ _builder_stream_vtable := io.Stream_VTable{
|
|||||||
},
|
},
|
||||||
impl_destroy = proc(s: io.Stream) -> io.Error {
|
impl_destroy = proc(s: io.Stream) -> io.Error {
|
||||||
b := (^Builder)(s.stream_data)
|
b := (^Builder)(s.stream_data)
|
||||||
delete(b.buf)
|
builder_destroy(b)
|
||||||
return .None
|
return .None
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// NOTE(dweiler): Work around a miscompilation bug on Linux still.
|
||||||
|
@(private)
|
||||||
|
_builder_stream_vtable := &_builder_stream_vtable_obj
|
||||||
|
/*
|
||||||
|
Returns an io.Stream from a Builder
|
||||||
|
|
||||||
// return an `io.Stream` from a builder
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An io.Stream
|
||||||
|
*/
|
||||||
to_stream :: proc(b: ^Builder) -> io.Stream {
|
to_stream :: proc(b: ^Builder) -> io.Stream {
|
||||||
return io.Stream{stream_vtable=&_builder_stream_vtable, stream_data=b}
|
return io.Stream{stream_vtable=_builder_stream_vtable, stream_data=b}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns an io.Writer from a Builder
|
||||||
|
|
||||||
// return an `io.Writer` from a builder
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An io.Writer
|
||||||
|
*/
|
||||||
to_writer :: proc(b: ^Builder) -> io.Writer {
|
to_writer :: proc(b: ^Builder) -> io.Writer {
|
||||||
return io.to_writer(to_stream(b))
|
return io.to_writer(to_stream(b))
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Deletes the Builder byte buffer content
|
||||||
|
|
||||||
// delete and clear the builder byte buffer content
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
*/
|
||||||
builder_destroy :: proc(b: ^Builder) {
|
builder_destroy :: proc(b: ^Builder) {
|
||||||
delete(b.buf)
|
delete(b.buf)
|
||||||
clear(&b.buf)
|
b.buf = nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Reserves the Builder byte buffer to a specific capacity, when it's higher than before
|
||||||
|
|
||||||
// reserve the builfer byte buffer to a specific cap, when it's higher than before
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- cap: The desired capacity for the Builder's buffer
|
||||||
|
*/
|
||||||
builder_grow :: proc(b: ^Builder, cap: int) {
|
builder_grow :: proc(b: ^Builder, cap: int) {
|
||||||
reserve(&b.buf, cap)
|
reserve(&b.buf, cap)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Clears the Builder byte buffer content (sets len to zero)
|
||||||
|
|
||||||
// clear the builder byte buffer content
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
*/
|
||||||
builder_reset :: proc(b: ^Builder) {
|
builder_reset :: proc(b: ^Builder) {
|
||||||
clear(&b.buf)
|
clear(&b.buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
create an empty builder with the same slice length as its cap
|
Creates a Builder from a slice of bytes with the same slice length as its capacity. Used in fmt.bprint*
|
||||||
uses the `mem.nil_allocator` to avoid allocation and keep a fixed length
|
|
||||||
used in `fmt.bprint*`
|
*Uses Nil Allocator - Does NOT allocate*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- backing: A slice of bytes to be used as the backing buffer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Builder
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
builder_from_bytes_example :: proc() {
|
||||||
|
bytes: [8]byte // <-- gets filled
|
||||||
|
builder := strings.builder_from_bytes(bytes[:])
|
||||||
|
strings.write_byte(&builder, 'a')
|
||||||
|
fmt.println(strings.to_string(builder)) // -> "a"
|
||||||
|
strings.write_byte(&builder, 'b')
|
||||||
|
fmt.println(strings.to_string(builder)) // -> "ab"
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
a
|
||||||
|
ab
|
||||||
|
|
||||||
bytes: [8]byte // <-- gets filled
|
|
||||||
builder := strings.builder_from_bytes(bytes[:])
|
|
||||||
strings.write_byte(&builder, 'a') -> "a"
|
|
||||||
strings.write_byte(&builder, 'b') -> "ab"
|
|
||||||
*/
|
*/
|
||||||
builder_from_bytes :: proc(backing: []byte) -> Builder {
|
builder_from_bytes :: proc(backing: []byte) -> Builder {
|
||||||
s := transmute(runtime.Raw_Slice)backing
|
s := transmute(runtime.Raw_Slice)backing
|
||||||
@@ -143,36 +257,84 @@ builder_from_bytes :: proc(backing: []byte) -> Builder {
|
|||||||
buf = transmute([dynamic]byte)d,
|
buf = transmute([dynamic]byte)d,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Alias to `builder_from_bytes`
|
||||||
builder_from_slice :: builder_from_bytes
|
builder_from_slice :: builder_from_bytes
|
||||||
|
/*
|
||||||
|
Casts the Builder byte buffer to a string and returns it
|
||||||
|
|
||||||
// cast the builder byte buffer to a string and return it
|
Inputs:
|
||||||
|
- b: A Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The contents of the Builder's buffer, as a string
|
||||||
|
*/
|
||||||
to_string :: proc(b: Builder) -> string {
|
to_string :: proc(b: Builder) -> string {
|
||||||
return string(b.buf[:])
|
return string(b.buf[:])
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns the length of the Builder's buffer, in bytes
|
||||||
|
|
||||||
// return the length of the builder byte buffer
|
Inputs:
|
||||||
|
- b: A Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The length of the Builder's buffer
|
||||||
|
*/
|
||||||
builder_len :: proc(b: Builder) -> int {
|
builder_len :: proc(b: Builder) -> int {
|
||||||
return len(b.buf)
|
return len(b.buf)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns the capacity of the Builder's buffer, in bytes
|
||||||
|
|
||||||
// return the cap of the builder byte buffer
|
Inputs:
|
||||||
|
- b: A Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The capacity of the Builder's buffer
|
||||||
|
*/
|
||||||
builder_cap :: proc(b: Builder) -> int {
|
builder_cap :: proc(b: Builder) -> int {
|
||||||
return cap(b.buf)
|
return cap(b.buf)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
The free space left in the Builder's buffer, in bytes
|
||||||
|
|
||||||
// returns the space left in the builder byte buffer to use up
|
Inputs:
|
||||||
|
- b: A Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The available space left in the Builder's buffer
|
||||||
|
*/
|
||||||
builder_space :: proc(b: Builder) -> int {
|
builder_space :: proc(b: Builder) -> int {
|
||||||
return cap(b.buf) - len(b.buf)
|
return cap(b.buf) - len(b.buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
appends a byte to the builder, returns the append diff
|
Appends a byte to the Builder and returns the number of bytes appended
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- x: The byte to be appended
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes appended
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_byte_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_byte(&builder, 'a') // 1
|
||||||
|
strings.write_byte(&builder, 'b') // 1
|
||||||
|
fmt.println(strings.to_string(builder)) // -> ab
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
ab
|
||||||
|
|
||||||
builder := strings.builder_make()
|
|
||||||
strings.write_byte(&builder, 'a') // 1
|
|
||||||
strings.write_byte(&builder, 'b') // 1
|
|
||||||
strings.write_byte(&builder, 'c') // 1
|
|
||||||
fmt.println(strings.to_string(builder)) // -> abc
|
|
||||||
*/
|
*/
|
||||||
write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
|
write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
|
||||||
n0 := len(b.buf)
|
n0 := len(b.buf)
|
||||||
@@ -180,14 +342,29 @@ write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
|
|||||||
n1 := len(b.buf)
|
n1 := len(b.buf)
|
||||||
return n1-n0
|
return n1-n0
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
appends a slice of bytes to the builder, returns the append diff
|
Appends a slice of bytes to the Builder and returns the number of bytes appended
|
||||||
|
|
||||||
builder := strings.builder_make()
|
Inputs:
|
||||||
bytes := [?]byte { 'a', 'b', 'c' }
|
- b: A pointer to the Builder
|
||||||
strings.write_bytes(&builder, bytes[:]) // 3
|
- x: The slice of bytes to be appended
|
||||||
fmt.println(strings.to_string(builder)) // -> abc
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_bytes_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
bytes := [?]byte { 'a', 'b', 'c' }
|
||||||
|
strings.write_bytes(&builder, bytes[:]) // 3
|
||||||
|
fmt.println(strings.to_string(builder)) // -> abc
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes appended
|
||||||
*/
|
*/
|
||||||
write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
|
write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
|
||||||
n0 := len(b.buf)
|
n0 := len(b.buf)
|
||||||
@@ -195,42 +372,99 @@ write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
|
|||||||
n1 := len(b.buf)
|
n1 := len(b.buf)
|
||||||
return n1-n0
|
return n1-n0
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
appends a single rune into the builder, returns written rune size and an `io.Error`
|
Appends a single rune to the Builder and returns the number of bytes written and an `io.Error`
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- r: The rune to be appended
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes written and an io.Error (if any)
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_rune_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_rune(&builder, 'ä') // 2 None
|
||||||
|
strings.write_rune(&builder, 'b') // 1 None
|
||||||
|
fmt.println(strings.to_string(builder)) // -> äb
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
äb
|
||||||
|
|
||||||
builder := strings.builder_make()
|
|
||||||
strings.write_rune(&builder, 'ä') // 2 None
|
|
||||||
strings.write_rune(&builder, 'b') // 1 None
|
|
||||||
strings.write_rune(&builder, 'c') // 1 None
|
|
||||||
fmt.println(strings.to_string(builder)) // -> äbc
|
|
||||||
*/
|
*/
|
||||||
write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
|
write_rune :: proc(b: ^Builder, r: rune) -> (int, io.Error) {
|
||||||
return io.write_rune(to_writer(b), r)
|
return io.write_rune(to_writer(b), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
appends a quoted rune into the builder, returns written size
|
Appends a quoted rune to the Builder and returns the number of bytes written
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- r: The rune to be appended
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes written
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_quoted_rune_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_string(&builder, "abc") // 3
|
||||||
|
strings.write_quoted_rune(&builder, 'ä') // 4
|
||||||
|
strings.write_string(&builder, "abc") // 3
|
||||||
|
fmt.println(strings.to_string(builder)) // -> abc'ä'abc
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
abc'ä'abc
|
||||||
|
|
||||||
builder := strings.builder_make()
|
|
||||||
strings.write_string(&builder, "abc") // 3
|
|
||||||
strings.write_quoted_rune(&builder, 'ä') // 4
|
|
||||||
strings.write_string(&builder, "abc") // 3
|
|
||||||
fmt.println(strings.to_string(builder)) // -> abc'ä'abc
|
|
||||||
*/
|
*/
|
||||||
write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
|
write_quoted_rune :: proc(b: ^Builder, r: rune) -> (n: int) {
|
||||||
return io.write_quoted_rune(to_writer(b), r)
|
return io.write_quoted_rune(to_writer(b), r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
appends a string to the builder, return the written byte size
|
Appends a string to the Builder and returns the number of bytes written
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- s: The string to be appended
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes written
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_string_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_string(&builder, "a") // 1
|
||||||
|
strings.write_string(&builder, "bc") // 2
|
||||||
|
fmt.println(strings.to_string(builder)) // -> abc
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
abc
|
||||||
|
|
||||||
builder := strings.builder_make()
|
|
||||||
strings.write_string(&builder, "a") // 1
|
|
||||||
strings.write_string(&builder, "bc") // 2
|
|
||||||
strings.write_string(&builder, "xyz") // 3
|
|
||||||
fmt.println(strings.to_string(builder)) // -> abcxyz
|
|
||||||
*/
|
*/
|
||||||
write_string :: proc(b: ^Builder, s: string) -> (n: int) {
|
write_string :: proc(b: ^Builder, s: string) -> (n: int) {
|
||||||
n0 := len(b.buf)
|
n0 := len(b.buf)
|
||||||
@@ -238,10 +472,15 @@ write_string :: proc(b: ^Builder, s: string) -> (n: int) {
|
|||||||
n1 := len(b.buf)
|
n1 := len(b.buf)
|
||||||
return n1-n0
|
return n1-n0
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Pops and returns the last byte in the Builder or 0 when the Builder is empty
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
|
||||||
// pops and returns the last byte in the builder
|
Returns:
|
||||||
// returns 0 when the builder is empty
|
The last byte in the Builder or 0 if empty
|
||||||
|
*/
|
||||||
pop_byte :: proc(b: ^Builder) -> (r: byte) {
|
pop_byte :: proc(b: ^Builder) -> (r: byte) {
|
||||||
if len(b.buf) == 0 {
|
if len(b.buf) == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -252,9 +491,15 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) {
|
|||||||
d.len = max(d.len-1, 0)
|
d.len = max(d.len-1, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Pops the last rune in the Builder and returns the popped rune and its rune width or (0, 0) if empty
|
||||||
|
|
||||||
// pops the last rune in the builder and returns the popped rune and its rune width
|
Inputs:
|
||||||
// returns 0, 0 when the builder is empty
|
- b: A pointer to the Builder
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The popped rune and its rune width or (0, 0) if empty
|
||||||
|
*/
|
||||||
pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
||||||
if len(b.buf) == 0 {
|
if len(b.buf) == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
@@ -265,41 +510,116 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
|
|||||||
d.len = max(d.len-width, 0)
|
d.len = max(d.len-width, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
DIGITS_LOWER := "0123456789abcdefx"
|
DIGITS_LOWER := "0123456789abcdefx"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
append a quoted string into the builder, return the written byte size
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- str: The string to be quoted and appended
|
||||||
|
- quote: The optional quote character (default is double quotes)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes written
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_quoted_string_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_quoted_string(&builder, "a") // 3
|
||||||
|
strings.write_quoted_string(&builder, "bc", '\'') // 4
|
||||||
|
strings.write_quoted_string(&builder, "xyz") // 5
|
||||||
|
fmt.println(strings.to_string(builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
"a"'bc'"xyz"
|
||||||
|
|
||||||
builder := strings.builder_make()
|
|
||||||
strings.write_quoted_string(&builder, "a") // 3
|
|
||||||
strings.write_quoted_string(&builder, "bc", '\'') // 4
|
|
||||||
strings.write_quoted_string(&builder, "xyz") // 5
|
|
||||||
fmt.println(strings.to_string(builder)) // -> "a"'bc'xyz"
|
|
||||||
*/
|
*/
|
||||||
write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
|
write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
|
||||||
n, _ = io.write_quoted_string(to_writer(b), str, quote)
|
n, _ = io.write_quoted_string(to_writer(b), str, quote)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Appends a rune to the Builder and returns the number of bytes written
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- r: The rune to be appended
|
||||||
|
- write_quote: Optional boolean flag to wrap in single-quotes (') (default is true)
|
||||||
|
|
||||||
// appends a rune to the builder, optional `write_quote` boolean tag, returns the written rune size
|
Returns:
|
||||||
|
The number of bytes written
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_encoded_rune_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_encoded_rune(&builder, 'a', false) // 1
|
||||||
|
strings.write_encoded_rune(&builder, '\"', true) // 3
|
||||||
|
strings.write_encoded_rune(&builder, 'x', false) // 1
|
||||||
|
fmt.println(strings.to_string(builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
a'"'x
|
||||||
|
|
||||||
|
*/
|
||||||
write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
|
write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
|
||||||
n, _ = io.write_encoded_rune(to_writer(b), r, write_quote)
|
n, _ = io.write_encoded_rune(to_writer(b), r, write_quote)
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Appends an escaped rune to the Builder and returns the number of bytes written
|
||||||
|
|
||||||
// appends a rune to the builder, fully written out in case of escaped runes e.g. '\a' will be written as such
|
Inputs:
|
||||||
// when `r` and `quote` match and `quote` is `\\` - they will be written as two slashes
|
- b: A pointer to the Builder
|
||||||
// `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026`
|
- r: The rune to be appended
|
||||||
|
- quote: The quote character
|
||||||
|
- html_safe: Optional boolean flag to encode '<', '>', '&' as digits (default is false)
|
||||||
|
|
||||||
|
**Usage**
|
||||||
|
- '\a' will be written as such
|
||||||
|
- `r` and `quote` match and `quote` is `\\` - they will be written as two slashes
|
||||||
|
- `html_safe` flag in case the runes '<', '>', '&' should be encoded as digits e.g. `\u0026`
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of bytes written
|
||||||
|
*/
|
||||||
write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) {
|
write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) {
|
||||||
n, _ = io.write_escaped_rune(to_writer(b), r, quote, html_safe)
|
n, _ = io.write_escaped_rune(to_writer(b), r, quote, html_safe)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a f64 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a f64 value into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- f: The f64 value to be appended
|
||||||
|
- fmt: The format byte
|
||||||
|
- prec: The precision
|
||||||
|
- bit_size: The bit size
|
||||||
|
- always_signed: Optional boolean flag to always include the sign (default is false)
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
|
write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) {
|
||||||
buf: [384]byte
|
buf: [384]byte
|
||||||
s := strconv.append_float(buf[:], f, fmt, prec, bit_size)
|
s := strconv.append_float(buf[:], f, fmt, prec, bit_size)
|
||||||
@@ -310,8 +630,20 @@ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_
|
|||||||
}
|
}
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a f16 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a f16 value into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- f: The f16 value to be appended
|
||||||
|
- fmt: The format byte
|
||||||
|
- always_signed: Optional boolean flag to always include the sign
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
|
write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) {
|
||||||
buf: [384]byte
|
buf: [384]byte
|
||||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||||
@@ -320,8 +652,38 @@ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n:
|
|||||||
}
|
}
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a f32 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a f32 value into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- f: The f32 value to be appended
|
||||||
|
- fmt: The format byte
|
||||||
|
- always_signed: Optional boolean flag to always include the sign
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
write_f32_example :: proc() {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
strings.write_f32(&builder, 3.14159, 'f') // 6
|
||||||
|
strings.write_string(&builder, " - ") // 3
|
||||||
|
strings.write_f32(&builder, -0.123, 'e') // 8
|
||||||
|
fmt.println(strings.to_string(builder)) // -> 3.14159012 - -1.23000003e-01
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
3.14159012 - -1.23000003e-01
|
||||||
|
|
||||||
|
*/
|
||||||
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
|
write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) {
|
||||||
buf: [384]byte
|
buf: [384]byte
|
||||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||||
@@ -330,8 +692,20 @@ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n:
|
|||||||
}
|
}
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a f32 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a f64 value into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- f: The f32 value to be appended
|
||||||
|
- fmt: The format byte
|
||||||
|
- always_signed: Optional boolean flag to always include the sign
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
|
write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) {
|
||||||
buf: [384]byte
|
buf: [384]byte
|
||||||
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f))
|
||||||
@@ -340,30 +714,71 @@ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n:
|
|||||||
}
|
}
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a u64 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- i: The u64 value to be appended
|
||||||
|
- base: The optional base for the numeric representation
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
// writes a u64 value `i` in `base` = 10 into the builder, returns the written amount of characters
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
|
write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
|
||||||
buf: [32]byte
|
buf: [32]byte
|
||||||
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
|
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil)
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a i64 value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a i64 value `i` in `base` = 10 into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- i: The i64 value to be appended
|
||||||
|
- base: The optional base for the numeric representation
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
|
write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
|
||||||
buf: [32]byte
|
buf: [32]byte
|
||||||
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
|
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
|
||||||
return write_string(b, s)
|
return write_string(b, s)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a uint value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a uint value `i` in `base` = 10 into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- i: The uint value to be appended
|
||||||
|
- base: The optional base for the numeric representation
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
|
write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
|
||||||
return write_u64(b, u64(i), base)
|
return write_u64(b, u64(i), base)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes a int value to the Builder and returns the number of characters written
|
||||||
|
|
||||||
// writes a int value `i` in `base` = 10 into the builder, returns the written amount of characters
|
Inputs:
|
||||||
|
- b: A pointer to the Builder
|
||||||
|
- i: The int value to be appended
|
||||||
|
- base: The optional base for the numeric representation
|
||||||
|
|
||||||
|
NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` states the number actually written.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The number of characters written
|
||||||
|
*/
|
||||||
write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
|
write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
|
||||||
return write_i64(b, i64(i), base)
|
return write_i64(b, i64(i), base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+321
-74
@@ -4,6 +4,21 @@ import "core:io"
|
|||||||
import "core:unicode"
|
import "core:unicode"
|
||||||
import "core:unicode/utf8"
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Converts invalid UTF-8 sequences in the input string `s` to the `replacement` string.
|
||||||
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: Input string that may contain invalid UTF-8 sequences.
|
||||||
|
- replacement: String to replace invalid UTF-8 sequences with.
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
WARNING: Allocation does not occur when len(s) == 0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A valid UTF-8 string with invalid sequences replaced by `replacement`.
|
||||||
|
*/
|
||||||
to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string {
|
to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) -> string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return ""
|
return ""
|
||||||
@@ -33,7 +48,7 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) ->
|
|||||||
|
|
||||||
invalid := false
|
invalid := false
|
||||||
|
|
||||||
for i := 0; i < len(s); /**/ {
|
for i := 0; i < len(s); /**/{
|
||||||
c := s[i]
|
c := s[i]
|
||||||
if c < utf8.RUNE_SELF {
|
if c < utf8.RUNE_SELF {
|
||||||
i += 1
|
i += 1
|
||||||
@@ -57,13 +72,31 @@ to_valid_utf8 :: proc(s, replacement: string, allocator := context.allocator) ->
|
|||||||
}
|
}
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
returns the input string `s` with all runes set to lowered case
|
Converts the input string `s` to all lowercase characters.
|
||||||
always allocates using the `allocator`
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: Input string to be converted.
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new string with all characters converted to lowercase.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_lower_example :: proc() {
|
||||||
|
fmt.println(strings.to_lower("TeST"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
test
|
||||||
|
|
||||||
strings.to_lower("test") -> test
|
|
||||||
strings.to_lower("Test") -> test
|
|
||||||
*/
|
*/
|
||||||
to_lower :: proc(s: string, allocator := context.allocator) -> string {
|
to_lower :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
b: Builder
|
b: Builder
|
||||||
@@ -73,13 +106,31 @@ to_lower :: proc(s: string, allocator := context.allocator) -> string {
|
|||||||
}
|
}
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
returns the input string `s` with all runes set to upper case
|
Converts the input string `s` to all uppercase characters.
|
||||||
always allocates using the `allocator`
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: Input string to be converted.
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new string with all characters converted to uppercase.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_upper_example :: proc() {
|
||||||
|
fmt.println(strings.to_upper("Test"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
TEST
|
||||||
|
|
||||||
strings.to_lower("test") -> TEST
|
|
||||||
strings.to_lower("Test") -> TEST
|
|
||||||
*/
|
*/
|
||||||
to_upper :: proc(s: string, allocator := context.allocator) -> string {
|
to_upper :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
b: Builder
|
b: Builder
|
||||||
@@ -89,21 +140,38 @@ to_upper :: proc(s: string, allocator := context.allocator) -> string {
|
|||||||
}
|
}
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Checks if the rune `r` is a delimiter (' ', '-', or '_').
|
||||||
|
|
||||||
// returns true when the `c` rune is a space, '-' or '_'
|
Inputs:
|
||||||
// useful when treating strings like words in a text editor or html paths
|
- r: Rune to check for delimiter status.
|
||||||
is_delimiter :: proc(c: rune) -> bool {
|
|
||||||
return c == '-' || c == '_' || is_space(c)
|
Returns:
|
||||||
|
True if `r` is a delimiter, false otherwise.
|
||||||
|
*/
|
||||||
|
is_delimiter :: proc(r: rune) -> bool {
|
||||||
|
return r == '-' || r == '_' || is_space(r)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Checks if the rune `r` is a non-alphanumeric or space character.
|
||||||
|
|
||||||
// returns true when the `r` rune is a non alpha or `unicode.is_space` rune
|
Inputs:
|
||||||
|
- r: Rune to check for separator status.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if `r` is a non-alpha or `unicode.is_space` rune.
|
||||||
|
*/
|
||||||
is_separator :: proc(r: rune) -> bool {
|
is_separator :: proc(r: rune) -> bool {
|
||||||
if r <= 0x7f {
|
if r <= 0x7f {
|
||||||
switch r {
|
switch r {
|
||||||
case '0'..='9': return false
|
case '0' ..= '9':
|
||||||
case 'a'..='z': return false
|
return false
|
||||||
case 'A'..='Z': return false
|
case 'a' ..= 'z':
|
||||||
case '_': return false
|
return false
|
||||||
|
case 'A' ..= 'Z':
|
||||||
|
return false
|
||||||
|
case '_':
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -115,12 +183,46 @@ is_separator :: proc(r: rune) -> bool {
|
|||||||
|
|
||||||
return unicode.is_space(r)
|
return unicode.is_space(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
iterator that loops through the string and calls the callback with the `prev`, `curr` and `next` rune
|
Iterates over a string, calling a callback for each rune with the previous, current, and next runes as arguments.
|
||||||
on empty string `s` the callback gets called once with empty runes
|
|
||||||
|
Inputs:
|
||||||
|
- w: An io.Writer to be used by the callback for writing output.
|
||||||
|
- s: The input string to be iterated over.
|
||||||
|
- callback: A procedure to be called for each rune in the string, with arguments (w: io.Writer, prev, curr, next: rune).
|
||||||
|
The callback can utilize the provided io.Writer to write output during the iteration.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:io"
|
||||||
|
|
||||||
|
string_case_iterator_example :: proc() {
|
||||||
|
my_callback :: proc(w: io.Writer, prev, curr, next: rune) {
|
||||||
|
fmt.println("my_callback", curr) // <-- Custom logic here
|
||||||
|
}
|
||||||
|
s := "hello"
|
||||||
|
b: strings.Builder
|
||||||
|
strings.builder_init_len(&b, len(s))
|
||||||
|
w := strings.to_writer(&b)
|
||||||
|
strings.string_case_iterator(w, s, my_callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
my_callback h
|
||||||
|
my_callback e
|
||||||
|
my_callback l
|
||||||
|
my_callback l
|
||||||
|
my_callback o
|
||||||
|
|
||||||
*/
|
*/
|
||||||
string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Writer, prev, curr, next: rune)) {
|
string_case_iterator :: proc(
|
||||||
|
w: io.Writer,
|
||||||
|
s: string,
|
||||||
|
callback: proc(w: io.Writer, prev, curr, next: rune),
|
||||||
|
) {
|
||||||
prev, curr: rune
|
prev, curr: rune
|
||||||
for next in s {
|
for next in s {
|
||||||
if curr == 0 {
|
if curr == 0 {
|
||||||
@@ -139,10 +241,20 @@ string_case_iterator :: proc(w: io.Writer, s: string, callback: proc(w: io.Write
|
|||||||
callback(w, prev, curr, 0)
|
callback(w, prev, curr, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Alias to `to_camel_case`
|
||||||
to_lower_camel_case :: to_camel_case
|
to_lower_camel_case :: to_camel_case
|
||||||
|
/*
|
||||||
|
Converts the input string `s` to "lowerCamelCase".
|
||||||
|
|
||||||
// converts the `s` string to "lowerCamelCase"
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: Input string to be converted.
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A "lowerCamelCase" formatted string.
|
||||||
|
*/
|
||||||
to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
s := s
|
s := s
|
||||||
s = trim_space(s)
|
s = trim_space(s)
|
||||||
@@ -164,10 +276,20 @@ to_camel_case :: proc(s: string, allocator := context.allocator) -> string {
|
|||||||
|
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
// Alias to `to_pascal_case`
|
||||||
to_upper_camel_case :: to_pascal_case
|
to_upper_camel_case :: to_pascal_case
|
||||||
|
/*
|
||||||
|
Converts the input string `s` to "UpperCamelCase" (PascalCase).
|
||||||
|
|
||||||
// converts the `s` string to "PascalCase"
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: Input string to be converted.
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A "PascalCase" formatted string.
|
||||||
|
*/
|
||||||
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
s := s
|
s := s
|
||||||
s = trim_space(s)
|
s = trim_space(s)
|
||||||
@@ -189,17 +311,44 @@ to_pascal_case :: proc(s: string, allocator := context.allocator) -> string {
|
|||||||
|
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
returns the `s` string to words seperated by the given `delimiter` rune
|
Returns a string converted to a delimiter-separated case with configurable casing
|
||||||
all runes will be upper or lowercased based on the `all_uppercase` bool
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- delimiter: The rune to be used as the delimiter between words
|
||||||
|
- all_upper_case: A boolean indicating if the output should be all uppercased (true) or lowercased (false)
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_delimiter_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_delimiter_case("Hello World", '_', false))
|
||||||
|
fmt.println(strings.to_delimiter_case("Hello World", ' ', true))
|
||||||
|
fmt.println(strings.to_delimiter_case("aBC", '_', false))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
hello_world
|
||||||
|
HELLO WORLD
|
||||||
|
a_bc
|
||||||
|
|
||||||
strings.to_delimiter_case("Hello World", '_', false) -> hello_world
|
|
||||||
strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD
|
|
||||||
strings.to_delimiter_case("Hello World", ' ', true) -> HELLO WORLD
|
|
||||||
strings.to_delimiter_case("aBC", '_', false) -> a_b_c
|
|
||||||
*/
|
*/
|
||||||
to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allocator := context.allocator) -> string {
|
to_delimiter_case :: proc(
|
||||||
|
s: string,
|
||||||
|
delimiter: rune,
|
||||||
|
all_upper_case: bool,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> string {
|
||||||
s := s
|
s := s
|
||||||
s = trim_space(s)
|
s = trim_space(s)
|
||||||
b: Builder
|
b: Builder
|
||||||
@@ -237,73 +386,171 @@ to_delimiter_case :: proc(s: string, delimiter: rune, all_upper_case: bool, allo
|
|||||||
|
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
converts the `s` string to "snake_case" with all runes lowercased
|
Converts a string to "snake_case" with all runes lowercased
|
||||||
|
|
||||||
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_snake_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_snake_case("HelloWorld"))
|
||||||
|
fmt.println(strings.to_snake_case("Hello World"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
hello_world
|
||||||
|
hello_world
|
||||||
|
|
||||||
strings.to_snake_case("HelloWorld") -> hello_world
|
|
||||||
strings.to_snake_case("Hello World") -> hello_world
|
|
||||||
*/
|
*/
|
||||||
to_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
return to_delimiter_case(s, '_', false, allocator)
|
return to_delimiter_case(s, '_', false, allocator)
|
||||||
}
|
}
|
||||||
|
// Alias for `to_upper_snake_case`
|
||||||
to_screaming_snake_case :: to_upper_snake_case
|
to_screaming_snake_case :: to_upper_snake_case
|
||||||
|
/*
|
||||||
|
Converts a string to "SNAKE_CASE" with all runes uppercased
|
||||||
|
|
||||||
// converts the `s` string to "SNAKE_CASE" with all runes uppercased
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_upper_snake_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_upper_snake_case("HelloWorld"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
HELLO_WORLD
|
||||||
|
|
||||||
|
*/
|
||||||
to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_upper_snake_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
return to_delimiter_case(s, '_', true, allocator)
|
return to_delimiter_case(s, '_', true, allocator)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Converts a string to "kebab-case" with all runes lowercased
|
||||||
|
|
||||||
// converts the `s` string to "kebab-case" with all runes lowercased
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_kebab_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_kebab_case("HelloWorld"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
hello-world
|
||||||
|
|
||||||
|
*/
|
||||||
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
return to_delimiter_case(s, '-', false, allocator)
|
return to_delimiter_case(s, '-', false, allocator)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Converts a string to "KEBAB-CASE" with all runes uppercased
|
||||||
|
|
||||||
// converts the `s` string to "KEBAB-CASE" with all runes uppercased
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_upper_kebab_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_upper_kebab_case("HelloWorld"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
HELLO-WORLD
|
||||||
|
|
||||||
|
*/
|
||||||
to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_upper_kebab_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
return to_delimiter_case(s, '-', true, allocator)
|
return to_delimiter_case(s, '-', true, allocator)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Converts a string to "Ada_Case"
|
||||||
|
|
||||||
// converts the `s` string to "Ada_case"
|
*Allocates Using Provided Allocator*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- s: The input string to be converted
|
||||||
|
- allocator: (default: context.allocator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The converted string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
to_ada_case_example :: proc() {
|
||||||
|
fmt.println(strings.to_ada_case("HelloWorld"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
Hello_World
|
||||||
|
|
||||||
|
*/
|
||||||
to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
|
to_ada_case :: proc(s: string, allocator := context.allocator) -> string {
|
||||||
delimiter :: '_'
|
|
||||||
|
|
||||||
s := s
|
s := s
|
||||||
s = trim_space(s)
|
s = trim_space(s)
|
||||||
b: Builder
|
b: Builder
|
||||||
builder_init(&b, 0, len(s), allocator)
|
builder_init(&b, 0, len(s), allocator)
|
||||||
w := to_writer(&b)
|
w := to_writer(&b)
|
||||||
|
|
||||||
prev, curr: rune
|
string_case_iterator(w, s, proc(w: io.Writer, prev, curr, next: rune) {
|
||||||
|
if !is_delimiter(curr) {
|
||||||
for next in s {
|
if is_delimiter(prev) || prev == 0 || (unicode.is_lower(prev) && unicode.is_upper(curr)) {
|
||||||
if is_delimiter(curr) {
|
if prev != 0 {
|
||||||
if !is_delimiter(prev) {
|
io.write_rune(w, '_')
|
||||||
io.write_rune(w, delimiter)
|
}
|
||||||
|
io.write_rune(w, unicode.to_upper(curr))
|
||||||
|
} else {
|
||||||
|
io.write_rune(w, unicode.to_lower(curr))
|
||||||
}
|
}
|
||||||
} else if unicode.is_upper(curr) {
|
|
||||||
if unicode.is_lower(prev) || (unicode.is_upper(prev) && unicode.is_lower(next)) {
|
|
||||||
io.write_rune(w, delimiter)
|
|
||||||
}
|
|
||||||
io.write_rune(w, unicode.to_upper(curr))
|
|
||||||
} else if curr != 0 {
|
|
||||||
io.write_rune(w, unicode.to_lower(curr))
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
prev = curr
|
|
||||||
curr = next
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(s) > 0 {
|
|
||||||
if unicode.is_upper(curr) && unicode.is_lower(prev) && prev != 0 {
|
|
||||||
io.write_rune(w, delimiter)
|
|
||||||
io.write_rune(w, unicode.to_upper(curr))
|
|
||||||
} else {
|
|
||||||
io.write_rune(w, unicode.to_lower(curr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return to_string(b)
|
return to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+61
-11
@@ -2,49 +2,99 @@ package strings
|
|||||||
|
|
||||||
import "core:runtime"
|
import "core:runtime"
|
||||||
|
|
||||||
// custom string entry struct
|
// Custom string entry struct
|
||||||
Intern_Entry :: struct {
|
Intern_Entry :: struct {
|
||||||
len: int,
|
len: int,
|
||||||
str: [1]byte, // string is allocated inline with the entry to keep allocations simple
|
str: [1]byte, // string is allocated inline with the entry to keep allocations simple
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Intern is a more memory efficient string map
|
||||||
|
|
||||||
// "intern" is a more memory efficient string map
|
Uses Specified Allocator for `Intern_Entry` strings
|
||||||
// `allocator` is used to allocate the actual `Intern_Entry` strings
|
|
||||||
|
Fields:
|
||||||
|
- allocator: The allocator used for the Intern_Entry strings
|
||||||
|
- entries: A map of strings to interned string entries
|
||||||
|
*/
|
||||||
Intern :: struct {
|
Intern :: struct {
|
||||||
allocator: runtime.Allocator,
|
allocator: runtime.Allocator,
|
||||||
entries: map[string]^Intern_Entry,
|
entries: map[string]^Intern_Entry,
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes the entries map and sets the allocator for the string entries
|
||||||
|
|
||||||
// initialize the entries map and set the allocator for the string entries
|
*Allocates Using Provided Allocators*
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- m: A pointer to the Intern struct to be initialized
|
||||||
|
- allocator: The allocator for the Intern_Entry strings (Default: context.allocator)
|
||||||
|
- map_allocator: The allocator for the map of entries (Default: context.allocator)
|
||||||
|
*/
|
||||||
intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator := context.allocator) {
|
intern_init :: proc(m: ^Intern, allocator := context.allocator, map_allocator := context.allocator) {
|
||||||
m.allocator = allocator
|
m.allocator = allocator
|
||||||
m.entries = make(map[string]^Intern_Entry, 16, map_allocator)
|
m.entries = make(map[string]^Intern_Entry, 16, map_allocator)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Frees the map and all its content allocated using the `.allocator`.
|
||||||
|
|
||||||
// free the map and all its content allocated using the `.allocator`
|
Inputs:
|
||||||
|
- m: A pointer to the Intern struct to be destroyed
|
||||||
|
*/
|
||||||
intern_destroy :: proc(m: ^Intern) {
|
intern_destroy :: proc(m: ^Intern) {
|
||||||
for _, value in m.entries {
|
for _, value in m.entries {
|
||||||
free(value, m.allocator)
|
free(value, m.allocator)
|
||||||
}
|
}
|
||||||
delete(m.entries)
|
delete(m.entries)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns an interned copy of the given text, adding it to the map if not already present.
|
||||||
|
|
||||||
// returns the `text` string from the intern map - gets set if it didnt exist yet
|
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||||
// the returned string lives as long as the map entry lives
|
|
||||||
|
Inputs:
|
||||||
|
- m: A pointer to the Intern struct
|
||||||
|
- text: The string to be interned
|
||||||
|
|
||||||
|
NOTE: The returned string lives as long as the map entry lives.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The interned string and an allocator error if any
|
||||||
|
*/
|
||||||
intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) {
|
intern_get :: proc(m: ^Intern, text: string) -> (str: string, err: runtime.Allocator_Error) {
|
||||||
entry := _intern_get_entry(m, text) or_return
|
entry := _intern_get_entry(m, text) or_return
|
||||||
#no_bounds_check return string(entry.str[:entry.len]), nil
|
#no_bounds_check return string(entry.str[:entry.len]), nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns an interned copy of the given text as a cstring, adding it to the map if not already present.
|
||||||
|
|
||||||
// returns the `text` cstring from the intern map - gets set if it didnt exist yet
|
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||||
// the returned cstring lives as long as the map entry lives
|
|
||||||
|
Inputs:
|
||||||
|
- m: A pointer to the Intern struct
|
||||||
|
- text: The string to be interned
|
||||||
|
|
||||||
|
NOTE: The returned cstring lives as long as the map entry lives
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The interned cstring and an allocator error if any
|
||||||
|
*/
|
||||||
intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) {
|
intern_get_cstring :: proc(m: ^Intern, text: string) -> (str: cstring, err: runtime.Allocator_Error) {
|
||||||
entry := _intern_get_entry(m, text) or_return
|
entry := _intern_get_entry(m, text) or_return
|
||||||
return cstring(&entry.str[0]), nil
|
return cstring(&entry.str[0]), nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Internal function to lookup whether the text string exists in the map, returns the entry
|
||||||
|
Sets and allocates the entry if it wasn't set yet
|
||||||
|
|
||||||
// looks up wether the `text` string exists in the map, returns the entry
|
*Allocate using the Intern's Allocator (First time string is seen only)*
|
||||||
// sets & allocates the entry if it wasnt set yet
|
|
||||||
|
Inputs:
|
||||||
|
- m: A pointer to the Intern struct
|
||||||
|
- text: The string to be looked up or interned
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The new or existing interned entry and an allocator error if any
|
||||||
|
*/
|
||||||
_intern_get_entry :: proc(m: ^Intern, text: string) -> (new_entry: ^Intern_Entry, err: runtime.Allocator_Error) #no_bounds_check {
|
_intern_get_entry :: proc(m: ^Intern, text: string) -> (new_entry: ^Intern_Entry, err: runtime.Allocator_Error) #no_bounds_check {
|
||||||
if prev, ok := m.entries[text]; ok {
|
if prev, ok := m.entries[text]; ok {
|
||||||
return prev, nil
|
return prev, nil
|
||||||
|
|||||||
+139
-22
@@ -4,59 +4,109 @@ import "core:io"
|
|||||||
import "core:unicode/utf8"
|
import "core:unicode/utf8"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
io stream data for a string reader that can read based on bytes or runes
|
io stream data for a string reader that can read based on bytes or runes
|
||||||
implements the vtable when using the io.Reader variants
|
implements the vtable when using the `io.Reader` variants
|
||||||
"read" calls advance the current reading offset `i`
|
"read" calls advance the current reading offset `i`
|
||||||
*/
|
*/
|
||||||
Reader :: struct {
|
Reader :: struct {
|
||||||
s: string, // read-only buffer
|
s: string, // read-only buffer
|
||||||
i: i64, // current reading index
|
i: i64, // current reading index
|
||||||
prev_rune: int, // previous reading index of rune or < 0
|
prev_rune: int, // previous reading index of rune or < 0
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a string Reader with the provided string
|
||||||
|
|
||||||
// init the reader to the string `s`
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
- s: The input string to be read
|
||||||
|
*/
|
||||||
reader_init :: proc(r: ^Reader, s: string) {
|
reader_init :: proc(r: ^Reader, s: string) {
|
||||||
r.s = s
|
r.s = s
|
||||||
r.i = 0
|
r.i = 0
|
||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Converts a Reader into an `io.Stream`
|
||||||
|
|
||||||
// returns a stream from the reader data
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An io.Stream for the given Reader
|
||||||
|
*/
|
||||||
reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
|
reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) {
|
||||||
s.stream_data = r
|
s.stream_data = r
|
||||||
s.stream_vtable = &_reader_vtable
|
s.stream_vtable = &_reader_vtable
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a string Reader and returns an `io.Reader` for the given string
|
||||||
|
|
||||||
// init a reader to the string `s` and return an io.Reader
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
- s: The input string to be read
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An io.Reader for the given string
|
||||||
|
*/
|
||||||
to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
|
to_reader :: proc(r: ^Reader, s: string) -> io.Reader {
|
||||||
reader_init(r, s)
|
reader_init(r, s)
|
||||||
rr, _ := io.to_reader(reader_to_stream(r))
|
rr, _ := io.to_reader(reader_to_stream(r))
|
||||||
return rr
|
return rr
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Initializes a string Reader and returns an `io.Reader_At` for the given string
|
||||||
|
|
||||||
// init a reader to the string `s` and return an io.Reader_At
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
- s: The input string to be read
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An `io.Reader_At` for the given string
|
||||||
|
*/
|
||||||
to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
|
to_reader_at :: proc(r: ^Reader, s: string) -> io.Reader_At {
|
||||||
reader_init(r, s)
|
reader_init(r, s)
|
||||||
rr, _ := io.to_reader_at(reader_to_stream(r))
|
rr, _ := io.to_reader_at(reader_to_stream(r))
|
||||||
return rr
|
return rr
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns the remaining length of the Reader
|
||||||
|
|
||||||
// remaining length of the reader
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The remaining length of the Reader
|
||||||
|
*/
|
||||||
reader_length :: proc(r: ^Reader) -> int {
|
reader_length :: proc(r: ^Reader) -> int {
|
||||||
if r.i >= i64(len(r.s)) {
|
if r.i >= i64(len(r.s)) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return int(i64(len(r.s)) - r.i)
|
return int(i64(len(r.s)) - r.i)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Returns the length of the string stored in the Reader
|
||||||
|
|
||||||
// returns the string length stored by the reader
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The length of the string stored in the Reader
|
||||||
|
*/
|
||||||
reader_size :: proc(r: ^Reader) -> i64 {
|
reader_size :: proc(r: ^Reader) -> i64 {
|
||||||
return i64(len(r.s))
|
return i64(len(r.s))
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Reads len(p) bytes from the Reader's string and copies into the provided slice.
|
||||||
|
|
||||||
// reads len(p) bytes into the slice from the string in the reader
|
Inputs:
|
||||||
// returns `n` amount of read bytes and an io.Error
|
- r: A pointer to a Reader struct
|
||||||
|
- p: A byte slice to copy data into
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- n: The number of bytes read
|
||||||
|
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||||
|
*/
|
||||||
reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
||||||
if r.i >= i64(len(r.s)) {
|
if r.i >= i64(len(r.s)) {
|
||||||
return 0, .EOF
|
return 0, .EOF
|
||||||
@@ -66,9 +116,18 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) {
|
|||||||
r.i += i64(n)
|
r.i += i64(n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Reads len(p) bytes from the Reader's string and copies into the provided slice, at the specified offset from the current index.
|
||||||
|
|
||||||
// reads len(p) bytes into the slice from the string in the reader at an offset
|
Inputs:
|
||||||
// returns `n` amount of read bytes and an io.Error
|
- r: A pointer to a Reader struct
|
||||||
|
- p: A byte slice to copy data into
|
||||||
|
- off: The offset from which to read
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- n: The number of bytes read
|
||||||
|
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||||
|
*/
|
||||||
reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
|
reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) {
|
||||||
if off < 0 {
|
if off < 0 {
|
||||||
return 0, .Invalid_Offset
|
return 0, .Invalid_Offset
|
||||||
@@ -82,8 +141,16 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Reads and returns a single byte from the Reader's string
|
||||||
|
|
||||||
// reads and returns a single byte - error when out of bounds
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- The byte read from the Reader
|
||||||
|
- err: An `io.Error` if an error occurs while reading, including `.EOF`, otherwise `nil` denotes success.
|
||||||
|
*/
|
||||||
reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
|
reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
|
||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
if r.i >= i64(len(r.s)) {
|
if r.i >= i64(len(r.s)) {
|
||||||
@@ -93,8 +160,15 @@ reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
|
|||||||
r.i += 1
|
r.i += 1
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Decrements the Reader's index (i) by 1
|
||||||
|
|
||||||
// decreases the reader offset - error when below 0
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An `io.Error` if `r.i <= 0` (`.Invalid_Unread`), otherwise `nil` denotes success.
|
||||||
|
*/
|
||||||
reader_unread_byte :: proc(r: ^Reader) -> io.Error {
|
reader_unread_byte :: proc(r: ^Reader) -> io.Error {
|
||||||
if r.i <= 0 {
|
if r.i <= 0 {
|
||||||
return .Invalid_Unread
|
return .Invalid_Unread
|
||||||
@@ -103,9 +177,18 @@ reader_unread_byte :: proc(r: ^Reader) -> io.Error {
|
|||||||
r.i -= 1
|
r.i -= 1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Reads and returns a single rune and its `size` from the Reader's string
|
||||||
|
|
||||||
// reads and returns a single rune and the rune size - error when out bounds
|
Inputs:
|
||||||
reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) {
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- rr: The rune read from the Reader
|
||||||
|
- size: The size of the rune in bytes
|
||||||
|
- err: An `io.Error` if an error occurs while reading
|
||||||
|
*/
|
||||||
|
reader_read_rune :: proc(r: ^Reader) -> (rr: rune, size: int, err: io.Error) {
|
||||||
if r.i >= i64(len(r.s)) {
|
if r.i >= i64(len(r.s)) {
|
||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
return 0, 0, .EOF
|
return 0, 0, .EOF
|
||||||
@@ -115,13 +198,21 @@ reader_read_rune :: proc(r: ^Reader) -> (ch: rune, size: int, err: io.Error) {
|
|||||||
r.i += 1
|
r.i += 1
|
||||||
return rune(c), 1, nil
|
return rune(c), 1, nil
|
||||||
}
|
}
|
||||||
ch, size = utf8.decode_rune_in_string(r.s[r.i:])
|
rr, size = utf8.decode_rune_in_string(r.s[r.i:])
|
||||||
r.i += i64(size)
|
r.i += i64(size)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Decrements the Reader's index (i) by the size of the last read rune
|
||||||
|
|
||||||
// decreases the reader offset by the last rune
|
Inputs:
|
||||||
// can only be used once and after a valid read_rune call
|
- r: A pointer to a Reader struct
|
||||||
|
|
||||||
|
WARNING: May only be used once and after a valid `read_rune` call
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An `io.Error` if an error occurs while unreading (`.Invalid_Unread`), else `nil` denotes success.
|
||||||
|
*/
|
||||||
reader_unread_rune :: proc(r: ^Reader) -> io.Error {
|
reader_unread_rune :: proc(r: ^Reader) -> io.Error {
|
||||||
if r.i <= 0 {
|
if r.i <= 0 {
|
||||||
return .Invalid_Unread
|
return .Invalid_Unread
|
||||||
@@ -133,8 +224,18 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error {
|
|||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Seeks the Reader's index to a new position
|
||||||
|
|
||||||
// seeks the reader offset to a wanted offset
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
- offset: The new offset position
|
||||||
|
- whence: The reference point for the new position (`.Start`, `.Current`, or `.End`)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- The absolute offset after seeking
|
||||||
|
- err: An `io.Error` if an error occurs while seeking (`.Invalid_Whence`, `.Invalid_Offset`)
|
||||||
|
*/
|
||||||
reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
|
reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
|
||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
abs: i64
|
abs: i64
|
||||||
@@ -155,8 +256,19 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E
|
|||||||
r.i = abs
|
r.i = abs
|
||||||
return abs, nil
|
return abs, nil
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Writes the remaining content of the Reader's string into the provided `io.Writer`
|
||||||
|
|
||||||
// writes the string content left to read into the io.Writer `w`
|
Inputs:
|
||||||
|
- r: A pointer to a Reader struct
|
||||||
|
- w: The io.Writer to write the remaining content into
|
||||||
|
|
||||||
|
WARNING: Panics if writer writes more bytes than remainig length of string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- n: The number of bytes written
|
||||||
|
- err: An io.Error if an error occurs while writing (`.Short_Write`)
|
||||||
|
*/
|
||||||
reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
||||||
r.prev_rune = -1
|
r.prev_rune = -1
|
||||||
if r.i >= i64(len(r.s)) {
|
if r.i >= i64(len(r.s)) {
|
||||||
@@ -175,7 +287,12 @@ reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
VTable containing implementations for various `io.Stream` methods
|
||||||
|
|
||||||
|
This VTable is used by the Reader struct to provide its functionality
|
||||||
|
as an `io.Stream`.
|
||||||
|
*/
|
||||||
@(private)
|
@(private)
|
||||||
_reader_vtable := io.Stream_VTable{
|
_reader_vtable := io.Stream_VTable{
|
||||||
impl_size = proc(s: io.Stream) -> i64 {
|
impl_size = proc(s: io.Stream) -> i64 {
|
||||||
|
|||||||
+1818
-497
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,7 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator, loc := #caller
|
|||||||
|
|
||||||
|
|
||||||
sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) {
|
sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
cmode: u32 = 0
|
cmode: u32 = 0
|
||||||
cflags: u32 = 0
|
cflags: u32 = 0
|
||||||
@@ -132,30 +133,35 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
sys_mkdir :: proc(path: string, mode: Permission) -> bool {
|
sys_mkdir :: proc(path: string, mode: Permission) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cflags := _sys_permission_mode(mode)
|
cflags := _sys_permission_mode(mode)
|
||||||
return syscall_mkdir(cpath, cflags) != -1
|
return syscall_mkdir(cpath, cflags) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_mkdir_at :: proc(fd: c.int, path: string, mode: Permission) -> bool {
|
sys_mkdir_at :: proc(fd: c.int, path: string, mode: Permission) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cflags := _sys_permission_mode(mode)
|
cflags := _sys_permission_mode(mode)
|
||||||
return syscall_mkdir_at(fd, cpath, cflags) != -1
|
return syscall_mkdir_at(fd, cpath, cflags) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_rmdir :: proc(path: string, mode: Permission) -> bool {
|
sys_rmdir :: proc(path: string, mode: Permission) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cflags := _sys_permission_mode(mode)
|
cflags := _sys_permission_mode(mode)
|
||||||
return syscall_rmdir(cpath, cflags) != -1
|
return syscall_rmdir(cpath, cflags) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_rename :: proc(path: string, new_path: string) -> bool {
|
sys_rename :: proc(path: string, new_path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
|
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
|
||||||
return syscall_rename(cpath, cnpath) != -1
|
return syscall_rename(cpath, cnpath) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_rename_at :: proc(fd: c.int, path: string, to_fd: c.int, new_path: string) -> bool {
|
sys_rename_at :: proc(fd: c.int, path: string, to_fd: c.int, new_path: string) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
|
cnpath: cstring = clone_to_cstring(new_path, context.temp_allocator)
|
||||||
return syscall_rename_at(fd, cpath, to_fd, cnpath) != -1
|
return syscall_rename_at(fd, cpath, to_fd, cnpath) != -1
|
||||||
@@ -166,12 +172,14 @@ sys_lseek :: proc(fd: c.int, offset: i64, whence: Offset_From) -> i64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sys_chmod :: proc(path: string, mode: Permission) -> bool {
|
sys_chmod :: proc(path: string, mode: Permission) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
cmode := _sys_permission_mode(mode)
|
cmode := _sys_permission_mode(mode)
|
||||||
return syscall_chmod(cpath, cmode) != -1
|
return syscall_chmod(cpath, cmode) != -1
|
||||||
}
|
}
|
||||||
|
|
||||||
sys_lstat :: proc(path: string, status: ^stat) -> bool {
|
sys_lstat :: proc(path: string, status: ^stat) -> bool {
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
cpath: cstring = clone_to_cstring(path, context.temp_allocator)
|
||||||
return syscall_lstat(cpath, status) != -1
|
return syscall_lstat(cpath, status) != -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package darwin
|
package darwin
|
||||||
|
|
||||||
unix_offset_syscall :: proc(number: System_Call_Number) -> uintptr {
|
unix_offset_syscall :: proc "contextless" (number: System_Call_Number) -> uintptr {
|
||||||
return uintptr(number) + uintptr(0x2000000)
|
return uintptr(number) + uintptr(0x2000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user