Merge remote-tracking branch 'offical/bill/raddebugger-custom-section'

This commit is contained in:
ed
2025-05-25 09:25:29 -04:00
266 changed files with 20392 additions and 4511 deletions
+49 -47
View File
@@ -75,32 +75,35 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# MacOS 13 runs on Intel, 14 runs on ARM # MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14] os: [macos-13, macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v4
- name: Download LLVM (Linux) - uses: actions/checkout@v4
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS Intel) - name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13' if: matrix.os == 'macos-13'
run: | run: |
brew update brew update
brew install llvm@20 lua@5.4 lld brew install llvm@20 lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM) - name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
run: | run: |
brew update brew update
brew install llvm@20 wasmtime lua@5.4 lld brew install llvm@20 wasmtime lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Build Odin - name: Build Odin
run: ./build_odin.sh release run: ./build_odin.sh release
@@ -124,53 +127,52 @@ jobs:
- name: Odin check vendor/sdl3 - name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Normal Core library tests - name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests - name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests - name: Internals tests
run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests - name: GitHub Issue tests
run: | run: |
cd tests/issues cd tests/issues
./run.sh ./run.sh
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux i386
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux arm64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for FreeBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for OpenBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Run demo on WASI WASM32 - name: Run demo on WASI WASM32
run: | run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm wasmtime ./demo.wasm
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- name: Odin check vendor/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- name: Odin check vendor/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check vendor/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check vendor/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
build_windows: build_windows:
name: Windows Build, Check, and Test name: Windows Build, Check, and Test
runs-on: windows-2022 runs-on: windows-2022
@@ -215,23 +217,23 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests - name: Optimized core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll . copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues - name: Check issues
shell: cmd shell: cmd
run: | run: |
+2 -1
View File
@@ -293,6 +293,7 @@ build.sh
# RAD debugger project file # RAD debugger project file
*.raddbg *.raddbg
*.rdi
tests/issues/build/*
misc/featuregen/featuregen misc/featuregen/featuregen
codegen/build/gen_src.map codegen/build/gen_src.map
+1 -1
View File
@@ -43,7 +43,7 @@ There were additions made for quality of life reasons:
<img src="https://img.shields.io/discord/568138951836172421?logo=discord"> <img src="https://img.shields.io/discord/568138951836172421?logo=discord">
</a> </a>
<a href="https://github.com/odin-lang/odin/actions"> <a href="https://github.com/odin-lang/odin/actions">
<img src="https://github.com/odin-lang/odin/workflows/CI/badge.svg?branch=master&event=push"> <img src="https://github.com/odin-lang/odin/actions/workflows/ci.yml/badge.svg?branch=master&event=push">
</a> </a>
</p> </p>
+2 -1
View File
@@ -119,7 +119,8 @@ 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_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) --- expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T --- min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T --- max :: proc(values: ..T) -> T ---
+12 -2
View File
@@ -221,6 +221,9 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) --- type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) --- type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -274,8 +277,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
simd_reduce_add_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
@@ -298,7 +305,7 @@ simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)
simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) ---
simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) ---
simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -353,15 +360,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
objc_object :: struct{} objc_object :: struct{}
objc_selector :: struct{} objc_selector :: struct{}
objc_class :: struct{} objc_class :: struct{}
objc_ivar :: struct{}
objc_id :: ^objc_object objc_id :: ^objc_object
objc_SEL :: ^objc_selector objc_SEL :: ^objc_selector
objc_Class :: ^objc_class objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL --- objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class --- objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class ---
objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
+4 -1
View File
@@ -110,7 +110,6 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
types: []^Type_Info, types: []^Type_Info,
names: []string, names: []string,
} }
Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8] Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 { Type_Info_Struct_Flag :: enum u8 {
@@ -559,10 +558,14 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
Odin_Platform_Subtarget_Type :: enum int { Odin_Platform_Subtarget_Type :: enum int {
Default, Default,
iOS, iOS,
Android,
} }
*/ */
Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type]
/* /*
// Defined internally by the compiler // Defined internally by the compiler
Odin_Sanitizer_Flag :: enum u32 { Odin_Sanitizer_Flag :: enum u32 {
@@ -1,6 +1,7 @@
package runtime package runtime
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset) block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base)) block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed // Should be zeroed
assert(block.used == 0) assert(block.used == 0)
assert(block.prev == nil) assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil { if block_to_free != nil {
allocator := block_to_free.allocator allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc) mem_free(block_to_free, allocator, loc)
} }
} }
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return return
} }
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size block.used += size
return return
} }
@@ -164,6 +169,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
} }
arena.total_used = 0 arena.total_used = 0
} }
@@ -228,6 +234,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation // grow data in-place, adjusting next allocation
block.used = uint(new_end) block.used = uint(new_end)
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -301,6 +308,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero arena.total_used -= amount_to_zero
} }
+29
View File
@@ -1033,3 +1033,32 @@ default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> ui
h &= HASH_MASK h &= HASH_MASK
return uintptr(h) | uintptr(uintptr(h) == 0) return uintptr(h) | uintptr(uintptr(h) == 0)
} }
default_hasher_f64 :: proc "contextless" (f: f64, seed: uintptr) -> uintptr {
f := f
buf: [size_of(f)]u8
if f == 0 {
return default_hasher(&buf, seed, size_of(buf))
}
if f != f {
// TODO(bill): What should the logic be for NaNs?
return default_hasher(&f, seed, size_of(f))
}
return default_hasher(&f, seed, size_of(f))
}
default_hasher_complex128 :: proc "contextless" (x, y: f64, seed: uintptr) -> uintptr {
seed := seed
seed = default_hasher_f64(x, seed)
seed = default_hasher_f64(y, seed)
return seed
}
default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintptr) -> uintptr {
seed := seed
seed = default_hasher_f64(x, seed)
seed = default_hasher_f64(y, seed)
seed = default_hasher_f64(z, seed)
seed = default_hasher_f64(w, seed)
return seed
}
+10 -2
View File
@@ -1,5 +1,7 @@
package runtime package runtime
import "../sanitizer"
foreign import kernel32 "system:Kernel32.lib" foreign import kernel32 "system:Kernel32.lib"
@(private="file") @(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(ptr, size)
return ptr
} }
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
if new_size == 0 { if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
} }
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(new_ptr, new_size)
return new_ptr
} }
_heap_free :: proc "contextless" (ptr: rawptr) { _heap_free :: proc "contextless" (ptr: rawptr) {
if ptr == nil { if ptr == nil {
+8
View File
@@ -1106,3 +1106,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
dst[j>>3] |= the_bit<<(j&7) dst[j>>3] |= the_bit<<(j&7)
} }
} }
when .Address in ODIN_SANITIZER_FLAGS {
foreign {
@(require)
__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
}
}
+19 -6
View File
@@ -2,21 +2,34 @@
package runtime package runtime
@(priority_index=-1e6) @(priority_index=-1e6)
foreign import "system:Foundation.framework" foreign import ObjC "system:objc"
import "base:intrinsics" import "base:intrinsics"
objc_id :: ^intrinsics.objc_object objc_id :: ^intrinsics.objc_object
objc_Class :: ^intrinsics.objc_class objc_Class :: ^intrinsics.objc_class
objc_SEL :: ^intrinsics.objc_selector objc_SEL :: ^intrinsics.objc_selector
objc_Ivar :: ^intrinsics.objc_ivar
objc_BOOL :: bool
foreign Foundation {
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class --- objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
foreign ObjC {
sel_registerName :: proc "c" (name: cstring) -> objc_SEL --- sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 --- objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 --- objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) --- objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_registerClassPair :: proc "c" (cls : objc_Class) ---
class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
} }
+601
View File
@@ -0,0 +1,601 @@
#+no-instrumentation
package sanitizer
Address_Death_Callback :: #type proc "c" (pc: rawptr, bp: rawptr, sp: rawptr, addr: rawptr, is_write: i32, access_size: uint)
@(private="file")
ASAN_ENABLED :: .Address in ODIN_SANITIZER_FLAGS
@(private="file")
@(default_calling_convention="system")
foreign {
__asan_poison_memory_region :: proc(address: rawptr, size: uint) ---
__asan_unpoison_memory_region :: proc(address: rawptr, size: uint) ---
__sanitizer_set_death_callback :: proc(callback: Address_Death_Callback) ---
__asan_region_is_poisoned :: proc(begin: rawptr, size: uint) -> rawptr ---
__asan_address_is_poisoned :: proc(addr: rawptr) -> i32 ---
__asan_describe_address :: proc(addr: rawptr) ---
__asan_report_present :: proc() -> i32 ---
__asan_get_report_pc :: proc() -> rawptr ---
__asan_get_report_bp :: proc() -> rawptr ---
__asan_get_report_sp :: proc() -> rawptr ---
__asan_get_report_address :: proc() -> rawptr ---
__asan_get_report_access_type :: proc() -> i32 ---
__asan_get_report_access_size :: proc() -> uint ---
__asan_get_report_description :: proc() -> cstring ---
__asan_locate_address :: proc(addr: rawptr, name: rawptr, name_size: uint, region_address: ^rawptr, region_size: ^uint) -> cstring ---
__asan_get_alloc_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
__asan_get_free_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
__asan_get_shadow_mapping :: proc(shadow_scale: ^uint, shadow_offset: ^uint) ---
__asan_print_accumulated_stats :: proc() ---
__asan_get_current_fake_stack :: proc() -> rawptr ---
__asan_addr_is_in_fake_stack :: proc(fake_stack: rawptr, addr: rawptr, beg: ^rawptr, end: ^rawptr) -> rawptr ---
__asan_handle_no_return :: proc() ---
__asan_update_allocation_context :: proc(addr: rawptr) -> i32 ---
}
Address_Access_Type :: enum {
none,
read,
write,
}
Address_Located_Address :: struct {
category: string,
name: string,
region: []byte,
}
Address_Shadow_Mapping :: struct {
scale: uint,
offset: uint,
}
/*
Marks a slice as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the slice. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED {
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a slice as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the slice again. This procedure is not thread-safe because no two threads
can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a pointer as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region the pointer points to. This procedure is not thread-safe because no
two threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, size_of(T))
}
}
/*
Marks a pointer as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region the pointer points to again. This procedure is not thread-safe
because no two threads can poison or unpoison memory in the same memory region
region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, size_of(T))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED {
assert_contextless(len >= 0)
__asan_poison_memory_region(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, len)
}
}
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED {
assert_contextless(len >= 0)
__asan_unpoison_memory_region(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, len)
}
}
address_poison :: proc {
address_poison_slice,
address_poison_ptr,
address_poison_rawptr,
address_poison_rawptr_uint,
}
address_unpoison :: proc {
address_unpoison_slice,
address_unpoison_ptr,
address_unpoison_rawptr,
address_unpoison_rawptr_uint,
}
/*
Registers a callback to be run when asan detects a memory error right before terminating
the process.
This can be used for logging and/or debugging purposes.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
when ASAN_ENABLED {
__sanitizer_set_death_callback(callback)
}
}
/*
Checks if the memory region covered by the slice is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
} else {
return nil
}
}
/*
Checks if the memory region pointed to by the pointer is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(ptr, size_of(T))
} else {
return nil
}
}
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
when ASAN_ENABLED {
assert_contextless(len >= 0)
return __asan_region_is_poisoned(region, uint(len))
} else {
return nil
}
}
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(region, len)
} else {
return nil
}
}
address_region_is_poisoned :: proc {
address_region_is_poisoned_slice,
address_region_is_poisoned_ptr,
address_region_is_poisoned_rawptr,
address_region_is_poisoned_rawptr_uint,
}
/*
Checks if the address is poisoned.
If it is poisoned this procedure returns `true`, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
when ASAN_ENABLED {
return __asan_address_is_poisoned(address) != 0
} else {
return false
}
}
/*
Describes the sanitizer state for an address.
This procedure prints the description out to `stdout`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_describe_address :: proc "contextless" (address: rawptr) {
when ASAN_ENABLED {
__asan_describe_address(address)
}
}
/*
Returns `true` if an asan error has occured, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_report_present :: proc "contextless" () -> bool {
when ASAN_ENABLED {
return __asan_report_present() != 0
} else {
return false
}
}
/*
Returns the program counter register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_pc :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_pc()
} else {
return nil
}
}
/*
Returns the base pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_bp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_bp()
} else {
return nil
}
}
/*
Returns the stack pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_sp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_sp()
} else {
return nil
}
}
/*
Returns the report buffer address of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_address :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_report_address()
} else {
return nil
}
}
/*
Returns the address access type of an asan error.
If no asan error has occurd `.none` is returned.
When asan is not enabled this procedure returns `.none`.
*/
@(no_sanitize_address)
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
when ASAN_ENABLED {
if ! address_report_present() {
return .none
}
return __asan_get_report_access_type() == 0 ? .read : .write
} else {
return .none
}
}
/*
Returns the access size of an asan error.
If no asan error has occurd `0` is returned.
When asan is not enabled this procedure returns `0`.
*/
@(no_sanitize_address)
address_get_report_access_size :: proc "contextless" () -> uint {
when ASAN_ENABLED {
return __asan_get_report_access_size()
} else {
return 0
}
}
/*
Returns the bug description of an asan error.
If no asan error has occurd an empty string is returned.
When asan is not enabled this procedure returns an empty string.
*/
@(no_sanitize_address)
address_get_report_description :: proc "contextless" () -> string {
when ASAN_ENABLED {
return string(__asan_get_report_description())
} else {
return ""
}
}
/*
Returns asan information about the address provided, writing the category into `data`.
The information provided include:
* The category of the address, i.e. stack, global, heap, etc.
* The name of the variable this address belongs to
* The memory region of the address
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
when ASAN_ENABLED {
out_addr: rawptr
out_size: uint
str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
return { string(str), string(cstring(raw_data(data))), (cast([^]byte)out_addr)[:out_size] },
} else {
return { "", "", {} }
}
}
/*
Returns the allocation stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED {
out_thread: i32
__asan_get_alloc_stack(addr, raw_data(data), len(data), &out_thread)
return data, int(out_thread)
} else {
return {}, 0
}
}
/*
Returns the free stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED {
out_thread: i32
__asan_get_free_stack(addr, raw_data(data), len(data), &out_thread)
return data, int(out_thread)
} else {
return {}, 0
}
}
/*
Returns the current asan shadow memory mapping.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
when ASAN_ENABLED {
result: Address_Shadow_Mapping
__asan_get_shadow_mapping(&result.scale, &result.offset)
return result
} else {
return {}
}
}
/*
Prints asan statistics to `stderr`
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_print_accumulated_stats :: proc "contextless" () {
when ASAN_ENABLED {
__asan_print_accumulated_stats()
}
}
/*
Returns the address of the current fake stack used by asan.
This pointer can be then used for `address_is_in_fake_stack`.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_current_fake_stack :: proc "contextless" () -> rawptr {
when ASAN_ENABLED {
return __asan_get_current_fake_stack()
} else {
return nil
}
}
/*
Returns if an address belongs to a given fake stack and if so the region of the fake frame.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
when ASAN_ENABLED {
begin: rawptr
end: rawptr
if __asan_addr_is_in_fake_stack(fake_stack, addr, &begin, &end) == nil {
return {}, false
}
return ((cast([^]byte)begin)[:uintptr(end)-uintptr(begin)]), true
} else {
return {}, false
}
}
/*
Performs shadow memory cleanup for the current thread before a procedure with no return is called
i.e. a procedure such as `panic` and `os.exit`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_handle_no_return :: proc "contextless" () {
when ASAN_ENABLED {
__asan_handle_no_return()
}
}
/*
Updates the allocation stack trace for the given address.
Returns `true` if successful, otherwise it returns `false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
when ASAN_ENABLED {
return __asan_update_allocation_context(addr) != 0
} else {
return false
}
}
+38
View File
@@ -0,0 +1,38 @@
/*
The `sanitizer` package implements various procedures for interacting with sanitizers
from user code.
An odin project can be linked with various sanitizers to help identify various different
bugs. These sanitizers are:
## Address
Enabled with `-sanitize:address` when building an odin project.
The address sanitizer (asan) is a runtime memory error detector used to help find common memory
related bugs. Typically asan interacts with libc but Odin code can be marked up to interact
with the asan runtime to extend the memory error detection outside of libc using this package.
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
## Memory
Enabled with `-sanitize:memory` when building an odin project.
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
use of uninitialized memory. This is not a very common bug in Odin as by default everything is
set to zero when initialised (ZII).
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
## Thread
Enabled with `-sanitize:thread` when building an odin project.
The thread sanitizer is a runtime data race detector. It can be used to detect if multiple threads
are concurrently writing and accessing a memory location without proper syncronisation.
For more information about the thread sanitizer see: https://clang.llvm.org/docs/ThreadSanitizer.html
*/
package sanitizer
Binary file not shown.
+20 -25
View File
@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
) )
) )
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
set CURR_DATE_TIME=%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
goto have_git_hash_and_date
)
:skip_git_hash
pushd misc pushd misc
cl /nologo get-date.c cl /nologo get-date.c
popd for /f %%i in ('get-date') do (
for /f %%i in ('misc\get-date') do (
set CURR_DATE_TIME=%%i set CURR_DATE_TIME=%%i
rem Don't set GIT_SHA
) )
popd
:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4% set curr_year=%CURR_DATE_TIME:~0,4%
set curr_month=%CURR_DATE_TIME:~4,2% set curr_month=%CURR_DATE_TIME:~5,2%
set curr_day=%CURR_DATE_TIME:~6,2% set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic :: Make sure this is a decent name and not generic
set exe_name=odin.exe set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0 set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%" set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%" set odin_version_raw="dev-%V1%-%V2%"
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
rem Parse source code as utf-8 even on shift-jis and other codepages rem Parse source code as utf-8 even on shift-jis and other codepages
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170 rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8 set compiler_flags= %compiler_flags% /utf-8
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
set rc_flags=-nologo ^ set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
-DVF=%odin_version_full% -DNIGHTLY=%nightly%
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
set odin_version_raw=dev-%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
) else (
set rc_flags=%rc_flags% -DVP=%odin_version_raw%
)
:skip_git_hash
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
@@ -138,6 +132,7 @@ del *.ilk > NUL 2> NUL
rc %rc_flags% %odin_rc% rc %rc_flags% %odin_rc%
cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name% cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
if %errorlevel% neq 0 goto end_of_build
mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32" mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32"
if %errorlevel% neq 0 goto end_of_build if %errorlevel% neq 0 goto end_of_build
@@ -152,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL del *.obj > NUL 2> NUL
:end_of_build :end_of_build
+4 -1
View File
@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=} : ${LDFLAGS=}
: ${LLVM_CONFIG=} : ${LLVM_CONFIG=}
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14" CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm" LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD) GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\"" CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() { error() {
printf "ERROR: %s\n" "$1" printf "ERROR: %s\n" "$1"
+1 -1
View File
@@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) {
for b.r+utf8.UTF_MAX > b.w && for b.r+utf8.UTF_MAX > b.w &&
!utf8.full_rune(b.buf[b.r:b.w]) && !utf8.full_rune(b.buf[b.r:b.w]) &&
b.err == nil && b.err == nil &&
b.w-b.w < len(b.buf) { b.w-b.r < len(b.buf) {
_reader_read_new_chunk(b) or_return _reader_read_new_chunk(b) or_return
} }
@@ -278,19 +278,19 @@ Example:
iterate_next_example :: proc() { iterate_next_example :: proc() {
l: list.List l: list.List
one := My_Struct{value=1} one := My_Next_Struct{value=1}
two := My_Struct{value=2} two := My_Next_Struct{value=2}
list.push_back(&l, &one.node) list.push_back(&l, &one.node)
list.push_back(&l, &two.node) list.push_back(&l, &two.node)
it := list.iterator_head(l, My_Struct, "node") it := list.iterator_head(l, My_Next_Struct, "node")
for num in list.iterate_next(&it) { for num in list.iterate_next(&it) {
fmt.println(num.value) fmt.println(num.value)
} }
} }
My_Struct :: struct { My_Next_Struct :: struct {
node : list.Node, node : list.Node,
value: int, value: int,
} }
@@ -325,22 +325,22 @@ Example:
import "core:fmt" import "core:fmt"
import "core:container/intrusive/list" import "core:container/intrusive/list"
iterate_next_example :: proc() { iterate_prev_example :: proc() {
l: list.List l: list.List
one := My_Struct{value=1} one := My_Prev_Struct{value=1}
two := My_Struct{value=2} two := My_Prev_Struct{value=2}
list.push_back(&l, &one.node) list.push_back(&l, &one.node)
list.push_back(&l, &two.node) list.push_back(&l, &two.node)
it := list.iterator_tail(l, My_Struct, "node") it := list.iterator_tail(l, My_Prev_Struct, "node")
for num in list.iterate_prev(&it) { for num in list.iterate_prev(&it) {
fmt.println(num.value) fmt.println(num.value)
} }
} }
My_Struct :: struct { My_Prev_Struct :: struct {
node : list.Node, node : list.Node,
value: int, value: int,
} }
+1 -1
View File
@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false return false
} }
_remove_node(c, e) _remove_node(c, e)
free(node, c.node_allocator) free(e, c.node_allocator)
c.count -= 1 c.count -= 1
return true return true
} }
@@ -1,6 +1,7 @@
package container_priority_queue package container_priority_queue
import "base:builtin" import "base:builtin"
import "base:runtime"
Priority_Queue :: struct($T: typeid) { Priority_Queue :: struct($T: typeid) {
queue: [dynamic]T, queue: [dynamic]T,
@@ -17,13 +18,14 @@ default_swap_proc :: proc($T: typeid) -> proc(q: []T, i, j: int) {
} }
} }
init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) { init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> (err: runtime.Allocator_Error) {
if pq.queue.allocator.procedure == nil { if pq.queue.allocator.procedure == nil {
pq.queue.allocator = allocator pq.queue.allocator = allocator
} }
reserve(pq, capacity) reserve(pq, capacity) or_return
pq.less = less pq.less = less
pq.swap = swap pq.swap = swap
return .None
} }
init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) { init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) {
@@ -41,8 +43,8 @@ destroy :: proc(pq: ^$Q/Priority_Queue($T)) {
delete(pq.queue) delete(pq.queue)
} }
reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) { reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) -> (err: runtime.Allocator_Error) {
builtin.reserve(&pq.queue, capacity) return builtin.reserve(&pq.queue, capacity)
} }
clear :: proc(pq: ^$Q/Priority_Queue($T)) { clear :: proc(pq: ^$Q/Priority_Queue($T)) {
builtin.clear(&pq.queue) builtin.clear(&pq.queue)
@@ -103,9 +105,10 @@ fix :: proc(pq: ^$Q/Priority_Queue($T), i: int) {
} }
} }
push :: proc(pq: ^$Q/Priority_Queue($T), value: T) { push :: proc(pq: ^$Q/Priority_Queue($T), value: T) -> (err: runtime.Allocator_Error) {
append(&pq.queue, value) append(&pq.queue, value) or_return
_shift_up(pq, builtin.len(pq.queue)-1) _shift_up(pq, builtin.len(pq.queue)-1)
return .None
} }
pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) { pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) {
@@ -130,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue) n := builtin.len(pq.queue)
if 0 <= i && i < n { if 0 <= i && i < n {
if n != i { pq.swap(pq.queue[:], i, n-1)
pq.swap(pq.queue[:], i, n) _shift_down(pq, i, n-1)
_shift_down(pq, i, n) _shift_up(pq, i)
_shift_up(pq, i) value, ok = builtin.pop(&pq.queue), true
}
value, ok = builtin.pop_safe(&pq.queue)
} }
return return
} }
+55
View File
@@ -0,0 +1,55 @@
/*
Package small_array implements a dynamic array like
interface on a stack-allocated, fixed-size array.
The Small_Array type is optimal for scenarios where you need
a container for a fixed number of elements of a specific type,
with the total number known at compile time but the exact
number to be used determined at runtime.
Example:
import "core:fmt"
import "core:container/small_array"
create :: proc() -> (result: small_array.Small_Array(10, rune)) {
// appending single elements
small_array.push(&result, 'e')
// pushing a bunch of elements at once
small_array.push(&result, 'l', 'i', 'x', '-', 'e')
// pre-pending
small_array.push_front(&result, 'H')
// removing elements
small_array.ordered_remove(&result, 4)
// resizing to the desired length (the capacity will stay unchanged)
small_array.resize(&result, 7)
// inserting elements
small_array.inject_at(&result, 'p', 5)
// updating elements
small_array.set(&result, 3, 'l')
// getting pointers to elements
o := small_array.get_ptr(&result, 4)
o^ = 'o'
// and much more ....
return
}
// the Small_Array can be an ordinary parameter 'generic' over
// the actual length to be usable with different sizes
print_elements :: proc(arr: ^small_array.Small_Array($N, rune)) {
for r in small_array.slice(arr) {
fmt.print(r)
}
}
main :: proc() {
arr := create()
// ...
print_elements(&arr)
}
Output:
Hellope
*/
package container_small_array
+607
View File
@@ -4,36 +4,171 @@ import "base:builtin"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
/*
A fixed-size stack-allocated array operated on in a dynamic fashion.
Fields:
- `data`: The underlying array
- `len`: Amount of items that the `Small_Array` currently holds
Example:
import "core:container/small_array"
example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push_back(&a, 10)
}
*/
Small_Array :: struct($N: int, $T: typeid) where N >= 0 { Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
data: [N]T, data: [N]T,
len: int, len: int,
} }
/*
Returns the amount of items in the small-array.
**Inputs**
- `a`: The small-array
**Returns**
- the amount of items in the array
*/
len :: proc "contextless" (a: $A/Small_Array) -> int { len :: proc "contextless" (a: $A/Small_Array) -> int {
return a.len return a.len
} }
/*
Returns the capacity of the small-array.
**Inputs**
- `a`: The small-array
**Returns** the capacity
*/
cap :: proc "contextless" (a: $A/Small_Array) -> int { cap :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data) return builtin.len(a.data)
} }
/*
Returns how many more items the small-array could fit.
**Inputs**
- `a`: The small-array
**Returns**
- the number of unused slots
*/
space :: proc "contextless" (a: $A/Small_Array) -> int { space :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data) - a.len return builtin.len(a.data) - a.len
} }
/*
Returns a slice of the data.
**Inputs**
- `a`: The pointer to the small-array
**Returns**
- the slice
Example:
import "core:container/small_array"
import "core:fmt"
slice_example :: proc() {
print :: proc(a: ^small_array.Small_Array($N, int)) {
for item in small_array.slice(a) {
fmt.println(item)
}
}
a: small_array.Small_Array(5, int)
small_array.push_back(&a, 1)
small_array.push_back(&a, 2)
print(&a)
}
Output:
1
2
*/
slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T { slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T {
return a.data[:a.len] return a.data[:a.len]
} }
/*
Get a copy of the item at the specified position.
This operation assumes that the small-array is large enough.
This will result in:
- the value if 0 <= index < len
- the zero value of the type if len < index < capacity
- 'crash' if capacity < index or index < 0
**Inputs**
- `a`: The small-array
- `index`: The position of the item to get
**Returns**
- the element at the specified position
*/
get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T { get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T {
return a.data[index] return a.data[index]
} }
/*
Get a pointer to the item at the specified position.
This operation assumes that the small-array is large enough.
This will result in:
- the pointer if 0 <= index < len
- the pointer to the zero value if len < index < capacity
- 'crash' if capacity < index or index < 0
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to get
**Returns**
- the pointer to the element at the specified position
*/
get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T { get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T {
return &a.data[index] return &a.data[index]
} }
/*
Attempt to get a copy of the item at the specified position.
**Inputs**
- `a`: The small-array
- `index`: The position of the item to get
**Returns**
- the element at the specified position
- true if element exists, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
get_safe_example :: proc() {
a: small_array.Small_Array(5, rune)
small_array.push_back(&a, 'A')
fmt.println(small_array.get_safe(a, 0) or_else 'x')
fmt.println(small_array.get_safe(a, 1) or_else 'x')
}
Output:
A
x
*/
get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check {
if index < 0 || index >= a.len { if index < 0 || index >= a.len {
return {}, false return {}, false
@@ -41,6 +176,17 @@ get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_
return a.data[index], true return a.data[index], true
} }
/*
Get a pointer to the item at the specified position.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to get
**Returns**
- the pointer to the element at the specified position
- true if element exists, false otherwise
*/
get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check {
if index < 0 || index >= a.len { if index < 0 || index >= a.len {
return {}, false return {}, false
@@ -48,15 +194,128 @@ get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_b
return &a.data[index], true return &a.data[index], true
} }
/*
Set the element at the specified position to the given value.
This operation assumes that the small-array is large enough.
This will result in:
- the value being set if 0 <= index < capacity
- 'crash' otherwise
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the item to set
- `value`: The value to set the element to
Example:
import "core:container/small_array"
import "core:fmt"
set_example :: proc() {
a: small_array.Small_Array(5, rune)
small_array.push_back(&a, 'A')
small_array.push_back(&a, 'B')
fmt.println(small_array.slice(&a))
// updates index 0
small_array.set(&a, 0, 'Z')
fmt.println(small_array.slice(&a))
// updates to a position x, where
// len <= x < cap are not visible since
// the length of the small-array remains unchanged
small_array.set(&a, 2, 'X')
small_array.set(&a, 3, 'Y')
small_array.set(&a, 4, 'Z')
fmt.println(small_array.slice(&a))
// resizing makes the change visible
small_array.resize(&a, 100)
fmt.println(small_array.slice(&a))
}
Output:
[A, B]
[Z, B]
[Z, B]
[Z, B, X, Y, Z]
*/
set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) { set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) {
a.data[index] = item a.data[index] = item
} }
/*
Tries to resize the small-array to the specified length.
The new length will be:
- `length` if `length` <= capacity
- capacity if length > capacity
**Inputs**
- `a`: A pointer to the small-array
- `length`: The new desired length
Example:
import "core:container/small_array"
import "core:fmt"
resize_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push_back(&a, 1)
small_array.push_back(&a, 2)
fmt.println(small_array.slice(&a))
small_array.resize(&a, 1)
fmt.println(small_array.slice(&a))
small_array.resize(&a, 100)
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
[1]
[1, 2, 0, 0, 0]
*/
resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { resize :: proc "contextless" (a: ^$A/Small_Array, length: int) {
a.len = min(length, builtin.len(a.data)) a.len = min(length, builtin.len(a.data))
} }
/*
Attempts to add the given element to the end.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_back_example :: proc() {
a: small_array.Small_Array(2, int)
assert(small_array.push_back(&a, 1), "this should fit")
assert(small_array.push_back(&a, 2), "this should fit")
assert(!small_array.push_back(&a, 3), "this should not fit")
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
*/
push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) { if a.len < cap(a^) {
a.data[a.len] = item a.data[a.len] = item
@@ -66,6 +325,39 @@ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false return false
} }
/*
Attempts to add the given element at the beginning.
This operation assumes that the small-array is not empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_front_example :: proc() {
a: small_array.Small_Array(2, int)
assert(small_array.push_front(&a, 2), "this should fit")
assert(small_array.push_front(&a, 1), "this should fit")
assert(!small_array.push_back(&a, 0), "this should not fit")
fmt.println(small_array.slice(&a))
}
Output:
[1, 2]
*/
push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) { if a.len < cap(a^) {
a.len += 1 a.len += 1
@@ -77,6 +369,35 @@ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false return false
} }
/*
Removes and returns the last element of the small-array.
This operation assumes that the small-array is not empty.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the end of the small-array
Example:
import "core:container/small_array"
import "core:fmt"
pop_back_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.pop_back(&a)
fmt.println("AFTER: ", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER: [0, 1]
*/
pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc) assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[a.len-1] item := a.data[a.len-1]
@@ -84,6 +405,38 @@ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) ->
return item return item
} }
/*
Removes and returns the first element of the small-array.
This operation assumes that the small-array is not empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the beginning of the small-array
Example:
import "core:container/small_array"
import "core:fmt"
pop_front_example :: proc() {
a: small_array.Small_Array(5, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.pop_front(&a)
fmt.println("AFTER: ", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER: [1, 2]
*/
pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc) assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[0] item := a.data[0]
@@ -93,6 +446,32 @@ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -
return item return item
} }
/*
Attempts to remove and return the last element of the small array.
Unlike `pop_back`, it does not assume that the array is non-empty.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the end of the small-array
- true if the small-array was not empty, false otherwise
Example:
import "core:container/small_array"
pop_back_safe_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 1)
el, ok := small_array.pop_back_safe(&a)
assert(ok, "there was an element in the array")
el, ok = small_array.pop_back_safe(&a)
assert(!ok, "there was NO element in the array")
}
*/
pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 { if N > 0 && a.len > 0 {
item = a.data[a.len-1] item = a.data[a.len-1]
@@ -102,6 +481,35 @@ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok
return return
} }
/*
Attempts to remove and return the first element of the small array.
Unlike `pop_front`, it does not assume that the array is non-empty.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
**Returns**
- a copy of the element removed from the beginning of the small-array
- true if the small-array was not empty, false otherwise
Example:
import "core:container/small_array"
pop_front_safe_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 1)
el, ok := small_array.pop_front_safe(&a)
assert(ok, "there was an element in the array")
el, ok = small_array.pop_front_(&a)
assert(!ok, "there was NO element in the array")
}
*/
pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 { if N > 0 && a.len > 0 {
item = a.data[0] item = a.data[0]
@@ -113,11 +521,70 @@ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, o
return return
} }
/*
Decreases the length of the small-array by the given amount.
The elements are therefore not really removed and can be
recovered by calling `resize`.
Note: This procedure assumes that the array has a sufficient length.
**Inputs**
- `a`: A pointer to the small-array
- `count`: The amount the length should be reduced by
Example:
import "core:container/small_array"
import "core:fmt"
consume_example :: proc() {
a: small_array.Small_Array(3, int)
small_array.push(&a, 0, 1, 2)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.consume(&a, 2)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2]
AFTER : [0]
*/
consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) { consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
assert(condition=a.len >= count, loc=loc) assert(condition=a.len >= count, loc=loc)
a.len -= count a.len -= count
} }
/*
Removes the element at the specified index while retaining order.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the element to remove
Example:
import "core:container/small_array"
import "core:fmt"
ordered_remove_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.ordered_remove(&a, 1)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : [0, 2, 3]
*/
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len) runtime.bounds_check_error_loc(loc, index, a.len)
if index+1 < a.len { if index+1 < a.len {
@@ -126,6 +593,32 @@ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, lo
a.len -= 1 a.len -= 1
} }
/*
Removes the element at the specified index without retaining order.
**Inputs**
- `a`: A pointer to the small-array
- `index`: The position of the element to remove
Example:
import "core:container/small_array"
import "core:fmt"
unordered_remove_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.unordered_remove(&a, 1)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : [0, 3, 2]
*/
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len) runtime.bounds_check_error_loc(loc, index, a.len)
n := a.len-1 n := a.len-1
@@ -135,10 +628,63 @@ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int,
a.len -= 1 a.len -= 1
} }
/*
Sets the length of the small-array to 0.
**Inputs**
- `a`: A pointer to the small-array
Example:
import "core:container/small_array"
import "core:fmt"
clear_example :: proc() {
a: small_array.Small_Array(4, int)
small_array.push(&a, 0, 1, 2, 3)
fmt.println("BEFORE:", small_array.slice(&a))
small_array.clear(&a)
fmt.println("AFTER :", small_array.slice(&a))
}
Output:
BEFORE: [0, 1, 2, 3]
AFTER : []
*/
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
resize(a, 0) resize(a, 0)
} }
/*
Attempts to append all elements to the small-array returning
false if there is not enough space to fit all of them.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
- ..:
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_back_elems_example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push_back_elems(&a, 0, 1, 2, 3, 4)
fmt.println(small_array.slice(&a))
}
Output:
[0, 1, 2, 3, 4]
*/
push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool {
if a.len + builtin.len(items) <= cap(a^) { if a.len + builtin.len(items) <= cap(a^) {
n := copy(a.data[a.len:], items[:]) n := copy(a.data[a.len:], items[:])
@@ -148,6 +694,36 @@ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -
return false return false
} }
/*
Tries to insert an element at the specified position.
Note: Performing this operation will cause pointers obtained
through get_ptr(_save) to reference incorrect elements.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to insert
- `index`: The index to insert the item at
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
inject_at_example :: proc() {
arr: small_array.Small_Array(100, rune)
small_array.push(&arr, 'A', 'C', 'D')
small_array.inject_at(&arr, 'B', 1)
fmt.println(small_array.slice(&arr))
}
Output:
[A, B, C, D]
*/
inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check {
if a.len < cap(a^) && index >= 0 && index <= len(a^) { if a.len < cap(a^) && index >= 0 && index <= len(a^) {
a.len += 1 a.len += 1
@@ -160,7 +736,38 @@ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int
return false return false
} }
// Alias for `push_back`
append_elem :: push_back append_elem :: push_back
// Alias for `push_back_elems`
append_elems :: push_back_elems append_elems :: push_back_elems
/*
Tries to append the element(s) to the small-array.
**Inputs**
- `a`: A pointer to the small-array
- `item`: The item to append
- ..:
**Returns**
- true if there was enough space to fit the element, false otherwise
Example:
import "core:container/small_array"
import "core:fmt"
push_example :: proc() {
a: small_array.Small_Array(100, int)
small_array.push(&a, 0)
small_array.push(&a, 1, 2, 3, 4)
fmt.println(small_array.slice(&a))
}
Output:
[0, 1, 2, 3, 4]
*/
push :: proc{push_back, push_back_elems} push :: proc{push_back, push_back_elems}
// Alias for `push`
append :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems}
+7 -1
View File
@@ -29,6 +29,7 @@ an input.
unmarshal :: proc { unmarshal :: proc {
unmarshal_from_reader, unmarshal_from_reader,
unmarshal_from_string, unmarshal_from_string,
unmarshal_from_bytes,
} }
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
return return
} }
// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info.
unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc)
}
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
d := d d := d
@@ -487,7 +493,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
assign_array(d, &da, t.elem, length) or_return assign_array(d, &da, t.elem, length) or_return
+26 -28
View File
@@ -108,7 +108,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
it couldn't have been part of an XML tag body to be decoded here. it couldn't have been part of an XML tag body to be decoded here.
Keep in mind that we could already *be* inside a CDATA tag. Keep in mind that we could already *be* inside a CDATA tag.
If so, write `>` as a literal and continue. If so, write `<` as a literal and continue.
*/ */
if in_data { if in_data {
write_rune(&builder, '<') write_rune(&builder, '<')
@@ -119,11 +119,9 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
case ']': case ']':
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. // If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
if in_data { if in_data {
if t.read_offset + len(CDATA_END) < len(t.src) { if strings.has_prefix(t.src[t.offset:], CDATA_END) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { in_data = false
in_data = false t.read_offset += len(CDATA_END) - 1
t.read_offset += len(CDATA_END) - 1
}
} }
continue continue
} else { } else {
@@ -297,40 +295,40 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
assert(t != nil && t.r == '<') assert(t != nil && t.r == '<')
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None } if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { s := string(t.src[t.offset:])
t.read_offset += len(CDATA_START) - 1 if strings.has_prefix(s, CDATA_START) {
if .Unbox_CDATA in options && .Decode_CDATA in options { if .Unbox_CDATA in options && .Decode_CDATA in options {
// We're unboxing _and_ decoding CDATA // We're unboxing _and_ decoding CDATA
t.read_offset += len(CDATA_START) - 1
return true, .None return true, .None
} }
// CDATA is passed through. // CDATA is passed through. Scan until end of CDATA.
offset := t.offset start_offset := t.offset
t.read_offset += len(CDATA_START)
// Scan until end of CDATA.
for { for {
advance(t) or_return advance(t)
if t.r < 0 { return true, .CDATA_Not_Terminated } if t.r < 0 {
// error(t, offset, "[scan_string] CDATA was not terminated\n")
return true, .CDATA_Not_Terminated
}
if t.read_offset + len(CDATA_END) < len(t.src) { // Scan until the end of a CDATA tag.
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { if s = string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
t.read_offset += len(CDATA_END) - 1 t.read_offset += len(CDATA_END)
cdata := string(t.src[start_offset:t.read_offset])
cdata := string(t.src[offset : t.read_offset]) if .Unbox_CDATA in options {
cdata = cdata[len(CDATA_START):]
if .Unbox_CDATA in options { cdata = cdata[:len(cdata) - len(CDATA_END)]
cdata = cdata[len(CDATA_START):]
cdata = cdata[:len(cdata) - len(CDATA_END)]
}
write_string(builder, cdata)
return false, .None
} }
write_string(builder, cdata)
return false, .None
} }
} }
} else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
} else if strings.has_prefix(s, COMMENT_START) {
t.read_offset += len(COMMENT_START) t.read_offset += len(COMMENT_START)
// Comment is passed through by default. // Comment is passed through by default.
offset := t.offset offset := t.offset
+2 -2
View File
@@ -79,7 +79,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) { read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
meta_data = make([]Meta, int(capacity), allocator=allocator) meta_data = make([]Meta, int(capacity), allocator=allocator)
count := 0 count := 0
defer meta_data = meta_data[:count]
for &m in meta_data { for &m in meta_data {
m.name = read_name(r) or_return m.name = read_name(r) or_return
@@ -105,6 +104,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
count += 1 count += 1
} }
meta_data = meta_data[:count]
return return
} }
@@ -112,7 +112,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
stack_count := read_value(r, u32le) or_return stack_count := read_value(r, u32le) or_return
layer_count := 0 layer_count := 0
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc) layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
defer layers = layers[:layer_count]
for &layer in layers { for &layer in layers {
layer.name = read_name(r) or_return layer.name = read_name(r) or_return
layer.components = read_value(r, u8) or_return layer.components = read_value(r, u8) or_return
@@ -136,6 +135,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
layer_count += 1 layer_count += 1
} }
layers = layers[:layer_count]
return return
} }
+2 -2
View File
@@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
} }
} }
scan_espace :: proc(t: ^Tokenizer) -> bool { scan_escape :: proc(t: ^Tokenizer) -> bool {
switch t.r { switch t.r {
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f': case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
next_rune(t) next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
break break
} }
if r == '\\' { if r == '\\' {
scan_espace(t) scan_escape(t)
} }
} }
+14 -8
View File
@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
return prev return prev
} }
// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
// Example: `json:"key_name,omitempty"`
// This returns the first field as `json_name`, and the rest are returned as `extra`.
@(private) @(private)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
defer delete(key, p.allocator) defer delete(key, p.allocator)
unmarshal_expect_token(p, .Colon) unmarshal_expect_token(p, .Colon)
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 { if use_field_idx < 0 {
for field, field_idx in fields { for field, field_idx in fields {
if key == field.name { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if json_name == "" && key == field.name {
use_field_idx = field_idx use_field_idx = field_idx
break break
} }
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
} }
if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if (json_name == "" && field.name == key) || json_name == key {
offset = field.offset offset = field.offset
type = field.type type = field.type
found = true found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
if field_found { if field_found {
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
if field_test(field_used, offset) { if field_test(field_used, offset) {
return .Multiple_Use_Field return .Multiple_Use_Field
} }
+2 -2
View File
@@ -240,7 +240,7 @@ Example:
import "core:encoding/uuid" import "core:encoding/uuid"
import "core:fmt" import "core:fmt"
main :: proc() { generate_v8_hash_bytes_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string) fmt.println(my_uuid_string)
@@ -306,7 +306,7 @@ Example:
import "core:encoding/uuid" import "core:encoding/uuid"
import "core:fmt" import "core:fmt"
main :: proc() { generate_v8_hash_string_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string) fmt.println(my_uuid_string)
+18 -20
View File
@@ -16,6 +16,7 @@ package encoding_xml
import "core:fmt" import "core:fmt"
import "core:unicode" import "core:unicode"
import "core:unicode/utf8" import "core:unicode/utf8"
import "core:strings"
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
@@ -121,7 +122,7 @@ default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
pos := offset_to_pos(t, offset) pos := offset_to_pos(t, offset)
if t.err != nil { if t.err != nil {
t.err(pos, msg, ..args) t.err(pos=pos, fmt=msg, args=args)
} }
t.error_count += 1 t.error_count += 1
} }
@@ -268,32 +269,27 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
// Skip CDATA // Skip CDATA
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
if t.read_offset + len(CDATA_START) >= len(t.src) { if s := string(t.src[t.offset:]); !strings.has_prefix(s, CDATA_START) {
// Can't be the start of a CDATA tag.
return .None return .None
} }
if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { t.read_offset += len(CDATA_START)
t.read_offset += len(CDATA_START) offset := t.offset
offset := t.offset
cdata_scan: for { cdata_scan: for {
advance_rune(t) advance_rune(t)
if t.ch < 0 { if t.ch < 0 {
error(t, offset, "[scan_string] CDATA was not terminated\n") error(t, offset, "[scan_string] CDATA was not terminated\n")
return .Premature_EOF return .Premature_EOF
} }
// Scan until the end of a CDATA tag. // Scan until the end of a CDATA tag.
if t.read_offset + len(CDATA_END) < len(t.src) { if s := string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { t.read_offset += len(CDATA_END)
t.read_offset += len(CDATA_END) break cdata_scan
break cdata_scan
}
}
} }
} }
return return .None
} }
@(optimization_mode="favor_size") @(optimization_mode="favor_size")
@@ -393,6 +389,8 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
case '/': kind = .Slash case '/': kind = .Slash
case '-': kind = .Dash case '-': kind = .Dash
case ':': kind = .Colon case ':': kind = .Colon
case '[': kind = .Open_Bracket
case ']': kind = .Close_Bracket
case '"', '\'': case '"', '\'':
kind = .Invalid kind = .Invalid
+59 -44
View File
@@ -56,7 +56,7 @@ Option_Flag :: enum {
Option_Flags :: bit_set[Option_Flag; u16] Option_Flags :: bit_set[Option_Flag; u16]
Document :: struct { Document :: struct {
elements: [dynamic]Element, elements: [dynamic]Element `fmt:"v,element_count"`,
element_count: Element_ID, element_count: Element_ID,
prologue: Attributes, prologue: Attributes,
@@ -70,15 +70,15 @@ Document :: struct {
// If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live. // If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
// Otherwise they'll be in the element tree. // Otherwise they'll be in the element tree.
comments: [dynamic]string, comments: [dynamic]string `fmt:"-"`,
// Internal // Internal
tokenizer: ^Tokenizer, tokenizer: ^Tokenizer `fmt:"-"`,
allocator: mem.Allocator, allocator: mem.Allocator `fmt:"-"`,
// Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified. // Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
input: []u8, input: []u8 `fmt:"-"`,
strings_to_free: [dynamic]string, strings_to_free: [dynamic]string `fmt:"-"`,
} }
Element :: struct { Element :: struct {
@@ -195,7 +195,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
loop: for { loop: for {
skip_whitespace(t) skip_whitespace(t)
// NOTE(Jeroen): This is faster as a switch.
switch t.ch { switch t.ch {
case '<': case '<':
// Consume peeked `<` // Consume peeked `<`
@@ -306,9 +305,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
} }
} }
case .Open_Bracket:
// This could be a CDATA tag part of a tag's body. Unread the `<![`
t.offset -= 3
// Instead of calling `parse_body` here, we could also `continue loop`
// and fall through to the `case:` at the bottom of the outer loop.
// This makes the intent clearer.
parse_body(doc, element, opts) or_return
case: case:
error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next) error(t, t.offset, "Unexpected Token after <!: %#v", next)
return
} }
} else if open.kind == .Question { } else if open.kind == .Question {
@@ -341,38 +348,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
case: case:
// This should be a tag's body text. // This should be a tag's body text.
body_text := scan_string(t, t.offset) or_return parse_body(doc, element, opts) or_return
needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags
if !needs_processing {
append(&doc.elements[element].value, body_text)
continue
}
decode_opts := entity.XML_Decode_Options{}
if .Keep_Tag_Body_Comments not_in opts.flags {
decode_opts += { .Comment_Strip }
}
if .Decode_SGML_Entities not_in opts.flags {
decode_opts += { .No_Entity_Decode }
}
if .Unbox_CDATA in opts.flags {
decode_opts += { .Unbox_CDATA }
if .Decode_SGML_Entities in opts.flags {
decode_opts += { .Decode_CDATA }
}
}
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
if decode_err == .None {
append(&doc.elements[element].value, decoded)
append(&doc.strings_to_free, decoded)
} else {
append(&doc.elements[element].value, body_text)
}
} }
} }
@@ -457,8 +433,6 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E
t := doc.tokenizer t := doc.tokenizer
key := expect(t, .Ident) or_return key := expect(t, .Ident) or_return
offset = t.offset - len(key.text)
_ = expect(t, .Eq) or_return _ = expect(t, .Eq) or_return
value := expect(t, .String, multiline_string=true) or_return value := expect(t, .String, multiline_string=true) or_return
@@ -591,6 +565,47 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) {
return .None return .None
} }
parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error) {
assert(doc != nil)
context.allocator = doc.allocator
t := doc.tokenizer
body_text := scan_string(t, t.offset) or_return
needs_processing := .Unbox_CDATA in opts.flags
needs_processing |= .Decode_SGML_Entities in opts.flags
if !needs_processing {
append(&doc.elements[element].value, body_text)
return
}
decode_opts := entity.XML_Decode_Options{}
if .Keep_Tag_Body_Comments not_in opts.flags {
decode_opts += { .Comment_Strip }
}
if .Decode_SGML_Entities not_in opts.flags {
decode_opts += { .No_Entity_Decode }
}
if .Unbox_CDATA in opts.flags {
decode_opts += { .Unbox_CDATA }
if .Decode_SGML_Entities in opts.flags {
decode_opts += { .Decode_CDATA }
}
}
decoded, decode_err := entity.decode_xml(body_text, decode_opts)
if decode_err == .None {
append(&doc.elements[element].value, decoded)
append(&doc.strings_to_free, decoded)
} else {
append(&doc.elements[element].value, body_text)
}
return
}
Element_ID :: u32 Element_ID :: u32
new_element :: proc(doc: ^Document) -> (id: Element_ID) { new_element :: proc(doc: ^Document) -> (id: Element_ID) {
@@ -609,4 +624,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) {
cur := doc.element_count cur := doc.element_count
doc.element_count += 1 doc.element_count += 1
return cur return cur
} }
+1 -1
View File
@@ -95,7 +95,7 @@ Example:
import "core:flags" import "core:flags"
import "core:fmt" import "core:fmt"
subtag_example :: proc() { get_subtag_example :: proc() {
args_tag := "precision=3,signed" args_tag := "precision=3,signed"
precision, has_precision := flags.get_subtag(args_tag, "precision") precision, has_precision := flags.get_subtag(args_tag, "precision")
+7 -8
View File
@@ -1449,9 +1449,12 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
_fmt_float_as(fi, v, bit_size, verb, 'g', -1) _fmt_float_as(fi, v, bit_size, verb, 'g', -1)
case 'f', 'F': case 'f', 'F':
_fmt_float_as(fi, v, bit_size, verb, 'f', 3) _fmt_float_as(fi, v, bit_size, verb, 'f', 3)
case 'e', 'E': case 'e':
// BUG(): "%.3e" returns "3.000e+00" // BUG(): "%.3e" returns "3.000e+00"
_fmt_float_as(fi, v, bit_size, verb, 'e', 6) _fmt_float_as(fi, v, bit_size, verb, 'e', 6)
case 'E':
// BUG(): "%.3E" returns "3.000E+00"
_fmt_float_as(fi, v, bit_size, verb, 'E', 6)
case 'h', 'H': case 'h', 'H':
prev_fi := fi^ prev_fi := fi^
@@ -1802,11 +1805,8 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
e, is_enum := et.variant.(runtime.Type_Info_Enum) e, is_enum := et.variant.(runtime.Type_Info_Enum)
commas := 0 commas := 0
loop: for i in 0 ..< bit_size { loop: for i in transmute(bit_set[0..<128])bits {
if bits & (1<<i) == 0 { i := i64(i) + info.lower
continue loop
}
if commas > 0 { if commas > 0 {
io.write_string(fi.writer, ", ", &fi.n) io.write_string(fi.writer, ", ", &fi.n)
} }
@@ -1829,8 +1829,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
} }
} }
} }
v := i64(i) + info.lower io.write_i64(fi.writer, i, 10, &fi.n)
io.write_i64(fi.writer, v, 10, &fi.n)
commas += 1 commas += 1
} }
} }
+53 -11
View File
@@ -2,10 +2,12 @@
#+build !orca #+build !orca
package log package log
import "core:encoding/ansi" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:os" import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time" import "core:time"
Level_Headers := [?]string{ Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string, ident: string,
} }
@(private) global_subtract_stdout_options: Options
@(private) global_subtract_stderr_options: Options
@(init, private)
init_standard_stream_status :: proc() {
// NOTE(Feoramund): While it is technically possible for these streams to
// be redirected during the runtime of the program, the cost of checking on
// every single log message is not worth it to support such an
// uncommonly-used feature.
if terminal.color_enabled {
// This is done this way because it's possible that only one of these
// streams could be redirected to a file.
if !terminal.is_terminal(os.stdout) {
global_subtract_stdout_options = {.Terminal_Color}
}
if !terminal.is_terminal(os.stderr) {
global_subtract_stderr_options = {.Terminal_Color}
}
} else {
// Override any terminal coloring.
global_subtract_stdout_options = {.Terminal_Color}
global_subtract_stderr_options = {.Terminal_Color}
}
}
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = h data.file_handle = h
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{file_logger_proc, data, lowest, opt}
} }
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE data.file_handle = os.INVALID_HANDLE
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{console_logger_proc, data, lowest, opt}
} }
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator) free(log.data, allocator)
} }
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @(private)
data := cast(^File_Console_Logger_Data)logger_data _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:]) buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
} }
if data.ident != "" { if ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident) fmt.sbprintf(&buf, "[%s] ", ident)
} }
//TODO(Hoej): When we have better atomics and such, make this thread-safe //TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
} }
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
}
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = ---
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
} else {
h = os.stderr
options -= global_subtract_stderr_options
}
_file_console_logger_proc(h, data.ident, level, text, options, location)
}
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+3 -29
View File
@@ -2296,7 +2296,7 @@ nextafter_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
case x == y: case x == y:
r = x r = x
case x == 0: case x == 0:
r = copy_sign_f16(1, y) r = copy_sign_f16(transmute(f16)u16(1), y)
case (y > x) == (x > 0): case (y > x) == (x > 0):
r = transmute(f16)(transmute(u16)x + 1) r = transmute(f16)(transmute(u16)x + 1)
case: case:
@@ -2312,7 +2312,7 @@ nextafter_f32 :: proc "contextless" (x, y: f32) -> (r: f32) {
case x == y: case x == y:
r = x r = x
case x == 0: case x == 0:
r = copy_sign_f32(1, y) r = copy_sign_f32(transmute(f32)u32(1), y)
case (y > x) == (x > 0): case (y > x) == (x > 0):
r = transmute(f32)(transmute(u32)x + 1) r = transmute(f32)(transmute(u32)x + 1)
case: case:
@@ -2328,7 +2328,7 @@ nextafter_f64 :: proc "contextless" (x, y: f64) -> (r: f64) {
case x == y: case x == y:
r = x r = x
case x == 0: case x == 0:
r = copy_sign_f64(1, y) r = copy_sign_f64(transmute(f64)u64(1), y)
case (y > x) == (x > 0): case (y > x) == (x > 0):
r = transmute(f64)(transmute(u64)x + 1) r = transmute(f64)(transmute(u64)x + 1)
case: case:
@@ -2349,32 +2349,6 @@ nextafter :: proc{
nextafter_f64, nextafter_f64le, nextafter_f64be, nextafter_f64, nextafter_f64le, nextafter_f64be,
} }
@(require_results)
signbit_f16 :: proc "contextless" (x: f16) -> bool {
return (transmute(u16)x)&(1<<15) != 0
}
@(require_results)
signbit_f32 :: proc "contextless" (x: f32) -> bool {
return (transmute(u32)x)&(1<<31) != 0
}
@(require_results)
signbit_f64 :: proc "contextless" (x: f64) -> bool {
return (transmute(u64)x)&(1<<63) != 0
}
@(require_results) signbit_f16le :: proc "contextless" (x: f16le) -> bool { return signbit_f16(f16(x)) }
@(require_results) signbit_f32le :: proc "contextless" (x: f32le) -> bool { return signbit_f32(f32(x)) }
@(require_results) signbit_f64le :: proc "contextless" (x: f64le) -> bool { return signbit_f64(f64(x)) }
@(require_results) signbit_f16be :: proc "contextless" (x: f16be) -> bool { return signbit_f16(f16(x)) }
@(require_results) signbit_f32be :: proc "contextless" (x: f32be) -> bool { return signbit_f32(f32(x)) }
@(require_results) signbit_f64be :: proc "contextless" (x: f64be) -> bool { return signbit_f64(f64(x)) }
signbit :: proc{
signbit_f16, signbit_f16le, signbit_f16be,
signbit_f32, signbit_f32le, signbit_f32be,
signbit_f64, signbit_f64le, signbit_f64be,
}
@(require_results) @(require_results)
hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) { hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
p, q := abs(x), abs(y) p, q := abs(x), abs(y)
+1 -1
View File
@@ -132,7 +132,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
case is_inf(x, 1): case is_inf(x, 1):
return inf_f64(1) return inf_f64(1)
case x == 0: case x == 0:
if signbit(x) { if sign_bit(x) {
return inf_f64(-1) return inf_f64(-1)
} }
return inf_f64(1) return inf_f64(1)
+3 -3
View File
@@ -33,7 +33,7 @@ Example:
import "core:math/rand" import "core:math/rand"
import "core:fmt" import "core:fmt"
set_global_seed_example :: proc() { reset_example :: proc() {
rand.reset(1) rand.reset(1)
fmt.println(rand.uint64()) fmt.println(rand.uint64())
} }
@@ -432,7 +432,7 @@ Example:
Possible Output: Possible Output:
15.312 15.312
673.130 273.15
*/ */
@(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) { @(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) {
@@ -467,7 +467,7 @@ Example:
Possible Output: Possible Output:
15.312 15.312
273.130 273.15
*/ */
@(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) { @(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) {
+43 -10
View File
@@ -2,6 +2,7 @@ package mem
import "base:intrinsics" import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "base:sanitizer"
/* /*
Nil allocator. Nil allocator.
@@ -138,6 +139,7 @@ arena_init :: proc(a: ^Arena, data: []byte) {
a.offset = 0 a.offset = 0
a.peak_used = 0 a.peak_used = 0
a.temp_count = 0 a.temp_count = 0
sanitizer.address_poison(a.data)
} }
/* /*
@@ -224,7 +226,9 @@ arena_alloc_bytes_non_zeroed :: proc(
} }
a.offset += total_size a.offset += total_size
a.peak_used = max(a.peak_used, a.offset) a.peak_used = max(a.peak_used, a.offset)
return byte_slice(ptr, size), nil result := byte_slice(ptr, size)
sanitizer.address_unpoison(result)
return result, nil
} }
/* /*
@@ -232,6 +236,7 @@ Free all memory to an arena.
*/ */
arena_free_all :: proc(a: ^Arena) { arena_free_all :: proc(a: ^Arena) {
a.offset = 0 a.offset = 0
sanitizer.address_poison(a.data)
} }
arena_allocator_proc :: proc( arena_allocator_proc :: proc(
@@ -309,6 +314,7 @@ allocations *inside* the temporary memory region will be freed to the arena.
end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) {
assert(tmp.arena.offset >= tmp.prev_offset) assert(tmp.arena.offset >= tmp.prev_offset)
assert(tmp.arena.temp_count > 0) assert(tmp.arena.temp_count > 0)
sanitizer.address_poison(tmp.arena.data[tmp.prev_offset:tmp.arena.offset])
tmp.arena.offset = tmp.prev_offset tmp.arena.offset = tmp.prev_offset
tmp.arena.temp_count -= 1 tmp.arena.temp_count -= 1
} }
@@ -363,6 +369,7 @@ scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocat
s.prev_allocation = nil s.prev_allocation = nil
s.backup_allocator = backup_allocator s.backup_allocator = backup_allocator
s.leaked_allocations.allocator = backup_allocator s.leaked_allocations.allocator = backup_allocator
sanitizer.address_poison(s.data)
return nil return nil
} }
@@ -377,6 +384,7 @@ scratch_destroy :: proc(s: ^Scratch) {
free_bytes(ptr, s.backup_allocator) free_bytes(ptr, s.backup_allocator)
} }
delete(s.leaked_allocations) delete(s.leaked_allocations)
sanitizer.address_unpoison(s.data)
delete(s.data, s.backup_allocator) delete(s.data, s.backup_allocator)
s^ = {} s^ = {}
} }
@@ -472,7 +480,9 @@ scratch_alloc_bytes_non_zeroed :: proc(
ptr := align_forward_uintptr(offset+start, uintptr(alignment)) ptr := align_forward_uintptr(offset+start, uintptr(alignment))
s.prev_allocation = rawptr(ptr) s.prev_allocation = rawptr(ptr)
s.curr_offset = int(offset) + size s.curr_offset = int(offset) + size
return byte_slice(rawptr(ptr), size), nil result := byte_slice(rawptr(ptr), size)
sanitizer.address_unpoison(result)
return result, nil
} else { } else {
a := s.backup_allocator a := s.backup_allocator
if a.procedure == nil { if a.procedure == nil {
@@ -516,6 +526,7 @@ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Alloc
old_ptr := uintptr(ptr) old_ptr := uintptr(ptr)
if s.prev_allocation == ptr { if s.prev_allocation == ptr {
s.curr_offset = int(uintptr(s.prev_allocation) - start) s.curr_offset = int(uintptr(s.prev_allocation) - start)
sanitizer.address_poison(s.data[s.curr_offset:])
s.prev_allocation = nil s.prev_allocation = nil
return nil return nil
} }
@@ -546,6 +557,7 @@ scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) {
free_bytes(ptr, s.backup_allocator, loc) free_bytes(ptr, s.backup_allocator, loc)
} }
clear(&s.leaked_allocations) clear(&s.leaked_allocations)
sanitizer.address_poison(s.data)
} }
/* /*
@@ -675,7 +687,9 @@ scratch_resize_bytes_non_zeroed :: proc(
old_ptr := uintptr(old_memory) old_ptr := uintptr(old_memory)
if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end {
s.curr_offset = int(old_ptr-begin)+size s.curr_offset = int(old_ptr-begin)+size
return byte_slice(old_memory, size), nil result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
} }
data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err != nil { if err != nil {
@@ -776,6 +790,7 @@ stack_init :: proc(s: ^Stack, data: []byte) {
s.prev_offset = 0 s.prev_offset = 0
s.curr_offset = 0 s.curr_offset = 0
s.peak_used = 0 s.peak_used = 0
sanitizer.address_poison(data)
} }
/* /*
@@ -861,15 +876,19 @@ stack_alloc_bytes_non_zeroed :: proc(
if s.curr_offset + padding + size > len(s.data) { if s.curr_offset + padding + size > len(s.data) {
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
old_offset := s.prev_offset
s.prev_offset = s.curr_offset s.prev_offset = s.curr_offset
s.curr_offset += padding s.curr_offset += padding
next_addr := curr_addr + uintptr(padding) next_addr := curr_addr + uintptr(padding)
header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header))
sanitizer.address_unpoison(header)
header.padding = padding header.padding = padding
header.prev_offset = s.prev_offset header.prev_offset = old_offset
s.curr_offset += size s.curr_offset += size
s.peak_used = max(s.peak_used, s.curr_offset) s.peak_used = max(s.peak_used, s.curr_offset)
return byte_slice(rawptr(next_addr), size), nil result := byte_slice(rawptr(next_addr), size)
sanitizer.address_unpoison(result)
return result, nil
} }
/* /*
@@ -902,12 +921,15 @@ stack_free :: proc(
} }
header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header))
old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data)))
if old_offset != header.prev_offset { if old_offset != s.prev_offset {
// panic("Out of order stack allocator free"); // panic("Out of order stack allocator free");
return .Invalid_Pointer return .Invalid_Pointer
} }
s.curr_offset = old_offset
s.prev_offset = header.prev_offset s.prev_offset = header.prev_offset
sanitizer.address_poison(s.data[old_offset:s.curr_offset])
s.curr_offset = old_offset
return nil return nil
} }
@@ -917,6 +939,7 @@ Free all allocations to the stack.
stack_free_all :: proc(s: ^Stack, loc := #caller_location) { stack_free_all :: proc(s: ^Stack, loc := #caller_location) {
s.prev_offset = 0 s.prev_offset = 0
s.curr_offset = 0 s.curr_offset = 0
sanitizer.address_poison(s.data)
} }
/* /*
@@ -1076,7 +1099,9 @@ stack_resize_bytes_non_zeroed :: proc(
if diff > 0 { if diff > 0 {
zero(rawptr(curr_addr + uintptr(diff)), diff) zero(rawptr(curr_addr + uintptr(diff)), diff)
} }
return byte_slice(old_memory, size), nil result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
} }
stack_allocator_proc :: proc( stack_allocator_proc :: proc(
@@ -1144,6 +1169,7 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) {
s.data = data s.data = data
s.offset = 0 s.offset = 0
s.peak_used = 0 s.peak_used = 0
sanitizer.address_poison(data)
} }
/* /*
@@ -1252,10 +1278,13 @@ small_stack_alloc_bytes_non_zeroed :: proc(
s.offset += padding s.offset += padding
next_addr := curr_addr + uintptr(padding) next_addr := curr_addr + uintptr(padding)
header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header))
sanitizer.address_unpoison(header)
header.padding = auto_cast padding header.padding = auto_cast padding
s.offset += size s.offset += size
s.peak_used = max(s.peak_used, s.offset) s.peak_used = max(s.peak_used, s.offset)
return byte_slice(rawptr(next_addr), size), nil result := byte_slice(rawptr(next_addr), size)
sanitizer.address_unpoison(result)
return result, nil
} }
/* /*
@@ -1289,6 +1318,7 @@ small_stack_free :: proc(
} }
header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header))
old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data)))
sanitizer.address_poison(s.data[old_offset:s.offset])
s.offset = old_offset s.offset = old_offset
return nil return nil
} }
@@ -1298,6 +1328,7 @@ Free all memory to small stack.
*/ */
small_stack_free_all :: proc(s: ^Small_Stack) { small_stack_free_all :: proc(s: ^Small_Stack) {
s.offset = 0 s.offset = 0
sanitizer.address_poison(s.data)
} }
/* /*
@@ -1442,7 +1473,9 @@ small_stack_resize_bytes_non_zeroed :: proc(
return nil, nil return nil, nil
} }
if old_size == size { if old_size == size {
return byte_slice(old_memory, size), nil result := byte_slice(old_memory, size)
sanitizer.address_unpoison(result)
return result, nil
} }
data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err == nil { if err == nil {
+31 -19
View File
@@ -1,6 +1,7 @@
package mem package mem
import "base:runtime" import "base:runtime"
import "base:sanitizer"
/* /*
Rollback stack default block size. Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
block_allocator: Allocator, block_allocator: Allocator,
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer) start := raw_data(block.buffer)
end := start[block.offset:] end := start[block.offset:]
return start < ptr && ptr <= end return start < ptr && ptr <= end
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
parent: ^Rollback_Stack_Block, parent: ^Rollback_Stack_Block,
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, nil, .Invalid_Pointer return nil, nil, nil, .Invalid_Pointer
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
header: ^Rollback_Stack_Header, header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, false return nil, nil, false
} }
@(private="file") @(private="file", no_sanitize_address)
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
header := header header := header
for block.offset > 0 && header.is_free { for block.offset > 0 && header.is_free {
block.offset = header.prev_offset block.offset = header.prev_offset
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
/* /*
Free memory to a rollback stack allocator. Free memory to a rollback stack allocator.
*/ */
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
parent, block, header := rb_find_ptr(stack, ptr) or_return parent, block, header := rb_find_ptr(stack, ptr) or_return
if header.is_free { if header.is_free {
return .Invalid_Pointer return .Invalid_Pointer
} }
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
/* /*
Free all memory owned by the rollback stack allocator. Free all memory owned by the rollback stack allocator.
*/ */
@(private="file") @(private="file", no_sanitize_address)
rb_free_all :: proc(stack: ^Rollback_Stack) { rb_free_all :: proc(stack: ^Rollback_Stack) {
for block := stack.head.next_block; block != nil; /**/ { for block := stack.head.next_block; block != nil; /**/ {
next_block := block.next_block next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil stack.head.next_block = nil
stack.head.last_alloc = nil stack.head.last_alloc = nil
stack.head.offset = 0 stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer)
} }
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc :: proc( rb_alloc :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes :: proc( rb_alloc_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_non_zeroed :: proc( rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes_non_zeroed :: proc( rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
assert(size >= 0, "Size must be positive or zero.", loc) assert(size >= 0, "Size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
parent: ^Rollback_Stack_Block parent: ^Rollback_Stack_Block
for block := stack.head; /**/; block = block.next_block { for block := stack.head; /**/; block = block.next_block {
when !ODIN_DISABLE_ASSERT { when !ODIN_DISABLE_ASSERT {
allocated_new_block: bool allocated_new_block: bool
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
// Prevent any further allocations on it. // Prevent any further allocations on it.
block.offset = cast(uintptr)len(block.buffer) block.offset = cast(uintptr)len(block.buffer)
} }
#no_bounds_check return ptr[:size], nil res := ptr[:size]
sanitizer.address_unpoison(res)
return res, nil
} }
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize :: proc( rb_resize :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes :: proc( rb_resize_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_non_zeroed :: proc( rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes_non_zeroed :: proc( rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
if len(block.buffer) <= stack.block_size { if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size block.offset += cast(uintptr)size - cast(uintptr)old_size
} }
#no_bounds_check return (ptr)[:size], nil res := (ptr)[:size]
sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
} }
} }
} }
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
return return
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
block = cast(^Rollback_Stack_Block)raw_data(buffer) block = cast(^Rollback_Stack_Block)raw_data(buffer)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
/* /*
Initialize the rollback stack allocator using a fixed backing buffer. Initialize the rollback stack allocator using a fixed backing buffer.
*/ */
@(no_sanitize_address)
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
/* /*
Initialize the rollback stack alocator using a backing block allocator. Initialize the rollback stack alocator using a backing block allocator.
*/ */
@(no_sanitize_address)
rollback_stack_init_dynamic :: proc( rollback_stack_init_dynamic :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
/* /*
Destroy a rollback stack. Destroy a rollback stack.
*/ */
@(no_sanitize_address)
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
if stack.block_allocator.procedure != nil { if stack.block_allocator.procedure != nil {
rb_free_all(stack) rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
the requested alignment. the requested alignment.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
return Allocator { return Allocator {
data = stack, data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
} }
} }
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator_proc :: proc( rollback_stack_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
+55 -15
View File
@@ -10,6 +10,7 @@
// package mem_tlsf implements a Two Level Segregated Fit memory allocator. // package mem_tlsf implements a Two Level Segregated Fit memory allocator.
package mem_tlsf package mem_tlsf
import "base:intrinsics"
import "base:runtime" import "base:runtime"
Error :: enum byte { Error :: enum byte {
@@ -21,7 +22,6 @@ Error :: enum byte {
Backing_Allocator_Error = 5, Backing_Allocator_Error = 5,
} }
Allocator :: struct { Allocator :: struct {
// Empty lists point at this block to indicate they are free. // Empty lists point at this block to indicate they are free.
block_null: Block_Header, block_null: Block_Header,
@@ -39,12 +39,13 @@ Allocator :: struct {
// statistics like how much memory is still available, // statistics like how much memory is still available,
// fragmentation, etc. // fragmentation, etc.
pool: Pool, pool: Pool,
// If we're expected to grow when we run out of memory,
// how much should we ask the backing allocator for?
new_pool_size: uint,
} }
#assert(size_of(Allocator) % ALIGN_SIZE == 0) #assert(size_of(Allocator) % ALIGN_SIZE == 0)
@(require_results) @(require_results)
allocator :: proc(t: ^Allocator) -> runtime.Allocator { allocator :: proc(t: ^Allocator) -> runtime.Allocator {
return runtime.Allocator{ return runtime.Allocator{
@@ -53,6 +54,21 @@ allocator :: proc(t: ^Allocator) -> runtime.Allocator {
} }
} }
// Tries to estimate a pool size sufficient for `count` allocations, each of `size` and with `alignment`.
estimate_pool_from_size_alignment :: proc(count: int, size: int, alignment: int) -> (pool_size: int) {
per_allocation := align_up(uint(size + alignment) + BLOCK_HEADER_OVERHEAD, ALIGN_SIZE)
return count * int(per_allocation) + int(INITIAL_POOL_OVERHEAD)
}
// Tries to estimate a pool size sufficient for `count` allocations of `type`.
estimate_pool_from_typeid :: proc(count: int, type: typeid) -> (pool_size: int) {
ti := type_info_of(type)
return estimate_pool_size(count, ti.size, ti.align)
}
estimate_pool_size :: proc{estimate_pool_from_size_alignment, estimate_pool_from_typeid}
@(require_results) @(require_results)
init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error { init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
assert(control != nil) assert(control != nil)
@@ -60,21 +76,25 @@ init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
return .Invalid_Alignment return .Invalid_Alignment
} }
pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE) pool_bytes := align_down(len(buf) - INITIAL_POOL_OVERHEAD, ALIGN_SIZE)
if pool_bytes < BLOCK_SIZE_MIN { if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX { } else if pool_bytes > BLOCK_SIZE_MAX {
return .Backing_Buffer_Too_Large return .Backing_Buffer_Too_Large
} }
clear(control) control.pool = Pool{
return pool_add(control, buf[:]) data = buf,
allocator = {},
}
return free_all(control)
} }
@(require_results) @(require_results)
init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error { init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error {
assert(control != nil) assert(control != nil)
pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE) pool_bytes := uint(estimate_pool_size(1, initial_pool_size, ALIGN_SIZE))
if pool_bytes < BLOCK_SIZE_MIN { if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX { } else if pool_bytes > BLOCK_SIZE_MAX {
@@ -85,12 +105,15 @@ init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, ini
if backing_err != nil { if backing_err != nil {
return .Backing_Allocator_Error return .Backing_Allocator_Error
} }
err := init_from_buffer(control, buf)
control.pool = Pool{ control.pool = Pool{
data = buf, data = buf,
allocator = backing, allocator = backing,
} }
return err
control.new_pool_size = uint(new_pool_size)
return free_all(control)
} }
init :: proc{init_from_buffer, init_from_allocator} init :: proc{init_from_buffer, init_from_allocator}
@@ -103,8 +126,6 @@ destroy :: proc(control: ^Allocator) {
// No need to call `pool_remove` or anything, as they're they're embedded in the backing memory. // No need to call `pool_remove` or anything, as they're they're embedded in the backing memory.
// We do however need to free the `Pool` tracking entities and the backing memory itself. // We do however need to free the `Pool` tracking entities and the backing memory itself.
// As `Allocator` is embedded in the first backing slice, the `control` pointer will be
// invalid after this call.
for p := control.pool.next; p != nil; { for p := control.pool.next; p != nil; {
next := p.next next := p.next
@@ -136,9 +157,8 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil return nil, nil
case .Free_All: case .Free_All:
// NOTE: this doesn't work right at the moment, Jeroen has it on his to-do list :) free_all(control)
// clear(control) return nil, nil
return nil, .Mode_Not_Implemented
case .Resize: case .Resize:
return resize(control, old_memory, uint(old_size), uint(size), uint(alignment)) return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
@@ -159,3 +179,23 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil return nil, nil
} }
// Exported solely to facilitate testing
@(require_results)
ffs :: proc "contextless" (word: u32) -> (bit: i32) {
return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
}
// Exported solely to facilitate testing
@(require_results)
fls :: proc "contextless" (word: u32) -> (bit: i32) {
N :: (size_of(u32) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(word))
}
// Exported solely to facilitate testing
@(require_results)
fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
N :: (size_of(uint) * 8) - 1
return i32(N - intrinsics.count_leading_zeros(size))
}
File diff suppressed because it is too large Load Diff
+10 -1
View File
@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
specified with `backing_allocator`. The `internals_allocator` will used to specified with `backing_allocator`. The `internals_allocator` will used to
allocate the tracked data. allocate the tracked data.
*/ */
@(no_sanitize_address)
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
/* /*
Destroy the tracking allocator. Destroy the tracking allocator.
*/ */
@(no_sanitize_address)
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
delete(t.allocation_map) delete(t.allocation_map)
delete(t.bad_free_array) delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
**Note**: This procedure clears only the current allocation data while keeping **Note**: This procedure clears only the current allocation data while keeping
the totals intact. the totals intact.
*/ */
@(no_sanitize_address)
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
Reset all of a Tracking Allocator's allocation data back to zero. Reset all of a Tracking Allocator's allocation data back to zero.
*/ */
@(no_sanitize_address)
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
example, you can use tracking_allocator_bad_free_callback_add_to_array to return example, you can use tracking_allocator_bad_free_callback_add_to_array to return
the tracking allocator to the old behavior, where the bad_free_array was used. the tracking allocator to the old behavior, where the bad_free_array was used.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location) runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ") runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this, Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
then you must make sure to check Tracking_Allocator.bad_free_array at some point. then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry { append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory, memory = memory,
@@ -175,7 +181,7 @@ Example:
} }
} }
*/ */
@(require_results) @(require_results, no_sanitize_address)
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
return Allocator{ return Allocator{
data = data, data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
} }
} }
@(no_sanitize_address)
tracking_allocator_proc :: proc( tracking_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
old_size: int, old_size: int,
loc := #caller_location, loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) { ) -> (result: []byte, err: Allocator_Error) {
@(no_sanitize_address)
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size) data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1 data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
} }
} }
@(no_sanitize_address)
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size) data.total_memory_freed += i64(entry.size)
data.total_free_count += 1 data.total_free_count += 1
+32 -11
View File
@@ -3,6 +3,8 @@ package mem_virtual
import "core:mem" import "core:mem"
import "core:sync" import "core:sync"
import "base:sanitizer"
Arena_Kind :: enum uint { Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list). Growing = 0, // Chained memory blocks (singly linked list).
Static = 1, // Fixed reservation sized. Static = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant. // Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory. // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
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
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 { if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved arena.minimum_block_size = reserved
} }
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Static` variant. // Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory. // A static arena contains a single `Memory_Block` allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0 arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Buffer` variant. // Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte. // A buffer arena contains single `Memory_Block` created from a user provided []byte.
@(require_results) @(require_results, no_sanitize_address)
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) {
return .Out_Of_Memory return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer arena.kind = .Buffer
mem.zero_slice(buffer) sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer) block_base := raw_data(buffer)
block := (^Memory_Block)(block_base) block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
} }
// Allocates memory from the provided arena. // Allocates memory from the provided arena.
@(require_results) @(require_results, no_sanitize_address)
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)
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
} }
sanitizer.address_unpoison(data)
return return
} }
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory. // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
@(no_sanitize_address)
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)
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
} }
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true return true
} else if pos == 0 { } else if pos == 0 {
arena.total_used = 0 arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
} }
// Frees the last memory block of a Growing Arena // Frees the last memory block of a Growing Arena
@(no_sanitize_address)
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
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)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block) memory_block_dealloc(free_block)
} }
} }
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0. // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
@(no_sanitize_address)
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind { switch arena.kind {
case .Growing: case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used) curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used) mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
} }
arena.total_used = 0 arena.total_used = 0
case .Static, .Buffer: case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena. // Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer. // A buffer based arena does not `delete` the provided `[]byte` bufffer.
@(no_sanitize_address)
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
switch arena.kind { switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
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
bootstrap.kind = .Growing bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
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 static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
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
bootstrap.kind = .Static bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
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` // Create an `Allocator` from the provided `Arena`
@(require_results) @(require_results, no_sanitize_address)
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 procedure used by an `Allocator` produced by `arena_allocator` // The allocator procedure used by an `Allocator` produced by `arena_allocator`
@(no_sanitize_address)
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,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size { if size < old_size {
// shrink data in-place // shrink data in-place
data = old_data[:size] data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
return return
} }
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used arena.total_used += block.used - prev_used
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return return
} }
copy(new_memory, old_data[:old_size]) copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size])
return new_memory, nil return new_memory, nil
case .Query_Features: case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory) set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
} }
// Begins the section of temporary arena memory. // Begins the section of temporary arena memory.
@(require_results) @(require_results, no_sanitize_address)
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)
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
} }
// Ends the section of temporary arena memory by resetting the memory to the stored position. // Ends the section of temporary arena memory by resetting the memory to the stored position.
@(no_sanitize_address)
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
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position. // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
@(no_sanitize_address)
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
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Asserts that all uses of `Arena_Temp` has been used by an `Arena` // Asserts that all uses of `Arena_Temp` has been used by an `Arena`
@(no_sanitize_address)
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)
} }
+17 -8
View File
@@ -2,6 +2,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
Allocator_Error :: mem.Allocator_Error Allocator_Error :: mem.Allocator_Error
@(require_results) @(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size) return _reserve(size)
} }
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
return _commit(data, size) return _commit(data, size)
} }
@(require_results) @(require_results, no_sanitize_address)
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
data = reserve(size) or_return data = reserve(size) or_return
commit(raw_data(data), size) or_return commit(raw_data(data), size) or_return
return return
} }
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) { decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
_decommit(data, size) _decommit(data, size)
} }
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) { release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
_release(data, size) _release(data, size)
} }
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
Protect_Flags :: distinct bit_set[Protect_Flag; u32] Protect_Flags :: distinct bit_set[Protect_Flag; u32]
Protect_No_Access :: Protect_Flags{} Protect_No_Access :: Protect_Flags{}
@(no_sanitize_address)
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
return _protect(data, size, flags) return _protect(data, size, flags)
} }
Memory_Block :: struct { Memory_Block :: struct {
prev: ^Memory_Block, prev: ^Memory_Block,
base: [^]byte, base: [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32] Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
result := size + align-1 result := size + align-1
return result - result%align return result - result%align
} }
@(require_results) @(require_results, no_sanitize_address)
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
page_size := DEFAULT_PAGE_SIZE page_size := DEFAULT_PAGE_SIZE
assert(mem.is_power_of_two(uintptr(page_size))) assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
return &pmblock.block, nil return &pmblock.block, nil
} }
@(require_results) @(require_results, no_sanitize_address)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
@(no_sanitize_address)
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0) alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:]) ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
return alignment_offset return alignment_offset
} }
@(no_sanitize_address)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size { if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block) pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
block.used += size block.used += size
sanitizer.address_unpoison(data)
return return
} }
@(no_sanitize_address)
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
if block := (^Platform_Memory_Block)(block_to_free); block != nil { if block := (^Platform_Memory_Block)(block_to_free); block != nil {
platform_memory_free(block) platform_memory_free(block)
+3
View File
@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
reserved: uint, reserved: uint,
} }
@(no_sanitize_address)
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) { platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
to_commit, to_reserve := to_commit, to_reserve to_commit, to_reserve := to_commit, to_reserve
to_reserve = max(to_commit, to_reserve) to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
} }
@(no_sanitize_address)
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) { platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
if block != nil { if block != nil {
release(block, block.reserved) release(block, block.reserved)
} }
} }
@(no_sanitize_address)
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) { platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
if to_commit < block.committed { if to_commit < block.committed {
return nil return nil
+11 -1
View File
@@ -83,6 +83,8 @@ foreign Kernel32 {
dwNumberOfBytesToMap: uint, dwNumberOfBytesToMap: uint,
) -> rawptr --- ) -> rawptr ---
} }
@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
return return
} }
@(no_sanitize_address)
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE) result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
} }
return nil return nil
} }
@(no_sanitize_address)
_decommit :: proc "contextless" (data: rawptr, size: uint) { _decommit :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, size, MEM_DECOMMIT) VirtualFree(data, size, MEM_DECOMMIT)
} }
@(no_sanitize_address)
_release :: proc "contextless" (data: rawptr, size: uint) { _release :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, 0, MEM_RELEASE) VirtualFree(data, 0, MEM_RELEASE)
} }
@(no_sanitize_address)
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
pflags: u32 pflags: u32
pflags = PAGE_NOACCESS pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
} }
@(no_sanitize_address)
_platform_memory_init :: proc() { _platform_memory_init :: proc() {
sys_info: SYSTEM_INFO sys_info: SYSTEM_INFO
GetSystemInfo(&sys_info) GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
} }
@(no_sanitize_address)
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
page_flags: u32 page_flags: u32
if flags == {.Read} { if flags == {.Read} {
+7 -9
View File
@@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
Maybe :: runtime.Maybe Maybe :: runtime.Maybe
Network_Error :: union #shared_nil { Network_Error :: union #shared_nil {
General_Error,
Platform_Error,
Create_Socket_Error, Create_Socket_Error,
Dial_Error, Dial_Error,
Listen_Error, Listen_Error,
@@ -65,6 +63,7 @@ Network_Error :: union #shared_nil {
TCP_Recv_Error, TCP_Recv_Error,
UDP_Recv_Error, UDP_Recv_Error,
Shutdown_Error, Shutdown_Error,
Interfaces_Error,
Socket_Option_Error, Socket_Option_Error,
Set_Blocking_Error, Set_Blocking_Error,
Parse_Endpoint_Error, Parse_Endpoint_Error,
@@ -74,14 +73,13 @@ Network_Error :: union #shared_nil {
#assert(size_of(Network_Error) == 8) #assert(size_of(Network_Error) == 8)
General_Error :: enum u32 { Interfaces_Error :: enum u32 {
None = 0, None,
Unable_To_Enumerate_Network_Interfaces = 1, Unable_To_Enumerate_Network_Interfaces,
Allocation_Failure,
Unknown,
} }
// `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 u32 { Parse_Endpoint_Error :: enum u32 {
None = 0, None = 0,
Bad_Port = 1, Bad_Port = 1,
@@ -109,7 +107,7 @@ TCP_Options :: struct {
no_delay: bool, no_delay: bool,
} }
default_tcp_options := TCP_Options { DEFAULT_TCP_OPTIONS :: TCP_Options {
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT, no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
} }
+275
View File
@@ -0,0 +1,275 @@
package net
/*
Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough.
Platforms specific returns:
- Darwin: `posix.Errno` (`core:sys/posix`)
- Linux: `linux.Errno` (`core:sys/linux`)
- FreeBSD: `freebsd.Errno` (`core:sys/freebsd`)
- Windows: `windows.System_Error` (`core:sys/windows`)
*/
@(require_results)
last_platform_error :: proc() -> i32 {
return _last_platform_error()
}
/*
Retrieve a stringified version of the last platform error.
*/
@(require_results)
last_platform_error_string :: proc() -> string {
return _last_platform_error_string()
}
set_last_platform_error :: proc(err: i32) {
_set_last_platform_error(err)
}
Create_Socket_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid/unsupported family or protocol.
Invalid_Argument,
// The user has no permission to create a socket of this type and/or protocol.
Insufficient_Permissions,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Dial_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid endpoint and/or options.
Invalid_Argument,
// An attempt was made to connect to a broadcast socket on a socket that doesn't support it.
Broadcast_Not_Supported,
// The socket is already connected.
Already_Connected,
// The socket is already in the progress of making a connection.
Already_Connecting,
// The address is already in use.
Address_In_Use,
// Could not reach the remote host.
Host_Unreachable,
// The remote host refused the connection or isn't listening.
Refused,
// The connection was reset by the remote host.
Reset,
// Timed out before making a connection.
Timeout,
// Non-blocking socket that would need to block waiting to connect.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Endpoint given without a port, which is required.
Port_Required,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Bind_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or endpoint, or invalid combination of the two.
Invalid_Argument,
// The socket is already bound to an address.
Already_Bound,
// The address is protected and the current user has insufficient permissions to access it.
Insufficient_Permissions_For_Address,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Listen_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// The socket or backlog is invalid.
Invalid_Argument,
// The socket is valid, but does not support listening.
Unsupported_Socket,
// The socket is already connected.
Already_Connected,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Accept_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket, or options.
Invalid_Argument,
// The given socket does not support accepting connections.
Unsupported_Socket,
// accept called on a socket which is not listening.
Not_Listening,
// A connection arrived but was closed while in the listen queue.
Aborted,
// Timed out before being able to accept a connection.
Timeout,
// Non-blocking socket that would need to block waiting for a connection.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// The socket is not connected.
Not_Connected,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// "Connection" was refused by remote, or closed/broken/shutdown while receiving data.
Connection_Refused,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded.
Excess_Truncated,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// The socket is not connected.
Not_Connected,
// Could not reach the remote host.
Host_Unreachable,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Could not reach the remote host.
Host_Unreachable,
// "Connection" was refused by remote, or closed/broken/shutdown while sending data.
Connection_Refused,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Shutdown_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid or not connected, or the manner given is invalid.
Invalid_Argument,
// Connection was closed/aborted/shutdown.
Connection_Closed,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Socket_Option_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Socket is invalid, not connected, or the connection has been closed/reset/shutdown.
Invalid_Socket,
// Unknown or unsupported option for the socket.
Invalid_Option,
// Invalid level or value.
Invalid_Value,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Set_Blocking_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid.
Invalid_Argument,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
+201 -161
View File
@@ -20,192 +20,232 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/posix" import "core:sys/posix"
@(private) _last_platform_error :: proc() -> i32 {
ESHUTDOWN :: 58 return i32(posix.errno())
Create_Socket_Error :: enum c.int {
None = 0,
Family_Not_Supported_For_This_Socket = c.int(posix.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(posix.EMFILE),
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
No_Memory_Available = c.int(posix.ENOMEM),
Protocol_Unsupported_By_System = c.int(posix.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(posix.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(posix.EPROTONOSUPPORT),
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(posix.errno())
Port_Required = -1, // Attempted to dial an endpointing without a port being set. return description
Address_In_Use = c.int(posix.EADDRINUSE),
In_Progress = c.int(posix.EINPROGRESS),
Cannot_Use_Any_Address = c.int(posix.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(posix.EAFNOSUPPORT),
Refused = c.int(posix.ECONNREFUSED),
Is_Listening_Socket = c.int(posix.EACCES),
Already_Connected = c.int(posix.EISCONN),
Network_Unreachable = c.int(posix.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(posix.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
Not_Socket = c.int(posix.ENOTSOCK),
Timeout = c.int(posix.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(posix.EWOULDBLOCK),
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, posix.errno(posix.Errno(err))
Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access.
Address_In_Use = c.int(posix.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(posix.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(posix.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(posix.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(posix.ENOBUFS), // There are not enough ephemeral ports available.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc() -> Create_Socket_Error {
None = 0, #partial switch posix.errno() {
Address_In_Use = c.int(posix.EADDRINUSE), case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE:
Already_Connected = c.int(posix.EISCONN), return .Insufficient_Resources
No_Socket_Descriptors_Available = c.int(posix.EMFILE), case .EAFNOSUPPORT, .EPROTOTYPE:
No_Buffer_Space_Available = c.int(posix.ENOBUFS), return .Invalid_Argument
Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), case .EACCES:
Not_Socket = c.int(posix.ENOTSOCK), return .Insufficient_Permissions
Listening_Not_Supported_For_This_Socket = c.int(posix.EOPNOTSUPP), case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc() -> Dial_Error {
None = 0, #partial switch posix.errno() {
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. case .ENOBUFS:
Reset = c.int(posix.ECONNRESET), return .Insufficient_Resources
Not_Listening = c.int(posix.EINVAL), case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL:
No_Socket_Descriptors_Available_For_Client_Socket = c.int(posix.EMFILE), return .Invalid_Argument
No_Buffer_Space_Available = c.int(posix.ENOBUFS), case .EISCONN:
Not_Socket = c.int(posix.ENOTSOCK), return .Already_Connected
Not_Connection_Oriented_Socket = c.int(posix.EOPNOTSUPP), case .EALREADY:
return .Already_Connecting
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? case .EADDRINUSE:
Would_Block = c.int(posix.EWOULDBLOCK), return .Address_In_Use
case .ENETDOWN:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
} }
TCP_Recv_Error :: enum c.int { _bind_error :: proc() -> Bind_Error {
None = 0, #partial switch posix.errno() {
Shutdown = ESHUTDOWN, case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP:
Not_Connected = c.int(posix.ENOTCONN), return .Invalid_Argument
case .EINVAL:
// TODO(tetra): Is this error actually possible here? return .Already_Bound
Connection_Broken = c.int(posix.ENETRESET), case .EACCES:
Not_Socket = c.int(posix.ENOTSOCK), return .Insufficient_Permissions_For_Address
Aborted = c.int(posix.ECONNABORTED), case .EADDRINUSE:
return .Address_In_Use
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? case:
Connection_Closed = c.int(posix.ECONNRESET), return .Unknown
Offline = c.int(posix.ENETDOWN), }
Host_Unreachable = c.int(posix.EHOSTUNREACH),
Interrupted = c.int(posix.EINTR),
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(posix.EWOULDBLOCK),
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc() -> Listen_Error {
None = 0, #partial switch posix.errno() {
Buffer_Too_Small = c.int(posix.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. case .EBADF, .ENOTSOCK:
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. return .Invalid_Argument
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. case .EDESTADDRREQ, .EOPNOTSUPP:
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. return .Unsupported_Socket
Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). case .EINVAL:
return .Already_Connected
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. case:
// NOTE: No, really. Presumably this means something different for nonblocking sockets... return .Unknown
Timeout = c.int(posix.EWOULDBLOCK), }
Socket_Not_Bound = c.int(posix.EINVAL), // The socket must be bound for this operation, but isn't.
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc() -> Accept_Error {
None = 0, #partial switch posix.errno() {
case .EMFILE, .ENFILE, .ENOMEM:
Aborted = c.int(posix.ECONNABORTED), return .Insufficient_Resources
Connection_Closed = c.int(posix.ECONNRESET), case .EBADF, .ENOTSOCK, .EFAULT:
Not_Connected = c.int(posix.ENOTCONN), return .Invalid_Argument
Shutdown = ESHUTDOWN, case .EOPNOTSUPP:
return .Unsupported_Socket
// The send queue was full. case .ECONNABORTED:
// This is usually a transient issue. return .Aborted
// case .EWOULDBLOCK:
// This also shouldn't normally happen on Linux, as data is dropped if it return .Would_Block
// doesn't fit in the send queue. case .EINTR:
No_Buffer_Space_Available = c.int(posix.ENOBUFS), return .Interrupted
Offline = c.int(posix.ENETDOWN), case:
Host_Unreachable = c.int(posix.EHOSTUNREACH), return .Unknown
Interrupted = c.int(posix.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(posix.EWOULDBLOCK),
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
} }
// TODO _tcp_recv_error :: proc() -> TCP_Recv_Error {
UDP_Send_Error :: enum c.int { #partial switch posix.errno() {
None = 0, case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP:
Message_Too_Long = c.int(posix.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. return .Invalid_Argument
case .ENOBUFS:
// TODO: not sure what the exact circumstances for this is yet return .Insufficient_Resources
Network_Unreachable = c.int(posix.ENETUNREACH), case .ENOTCONN:
No_Outbound_Ports_Available = c.int(posix.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. return .Not_Connected
case .ECONNRESET:
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. return .Connection_Closed
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case .ETIMEDOUT:
Timeout = c.int(posix.EWOULDBLOCK), return .Timeout
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket. case .EAGAIN:
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Would_Block
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory. case .EINTR:
Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Interrupted
case:
// The send queue was full. return .Unknown
// 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(posix.ENOBUFS),
No_Memory_Available = c.int(posix.ENOMEM), // No memory was available to properly manage the send queue.
} }
Shutdown_Manner :: enum c.int { _udp_recv_error :: proc() -> UDP_Recv_Error {
Receive = c.int(posix.SHUT_RD), #partial switch posix.errno() {
Send = c.int(posix.SHUT_WR), case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE:
Both = c.int(posix.SHUT_RDWR), return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _tcp_send_error :: proc() -> TCP_Send_Error {
None = 0, #partial switch posix.errno() {
Aborted = c.int(posix.ECONNABORTED), case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP:
Reset = c.int(posix.ECONNRESET), return .Invalid_Argument
Offline = c.int(posix.ENETDOWN), case .ENOBUFS:
Not_Connected = c.int(posix.ENOTCONN), return .Insufficient_Resources
Not_Socket = c.int(posix.ENOTSOCK), case .ECONNRESET, .EPIPE:
Invalid_Manner = c.int(posix.EINVAL), return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _udp_send_error :: proc() -> UDP_Send_Error {
None = 0, #partial switch posix.errno() {
Offline = c.int(posix.ENETDOWN), case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ:
Timeout_When_Keepalive_Set = c.int(posix.ENETRESET), return .Invalid_Argument
Invalid_Option_For_Socket = c.int(posix.ENOPROTOOPT), case .ENOBUFS, .ENOMEM:
Reset_When_Keepalive_Set = c.int(posix.ENOTCONN), return .Insufficient_Resources
Not_Socket = c.int(posix.ENOTSOCK), case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _shutdown_error :: proc() -> Shutdown_Error {
None = 0, #partial switch posix.errno() {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
// TODO: Add errors for `set_blocking` _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EISCONN:
return .Invalid_Socket
case .EINVAL, .ENOPROTOOPT:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch posix.errno() {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
} }
+234 -165
View File
@@ -20,198 +20,267 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/freebsd" import "core:sys/freebsd"
Create_Socket_Error :: enum c.int { @(private="file", thread_local)
None = 0, _last_error: freebsd.Errno
Access_Denied = cast(c.int)freebsd.Errno.EACCES,
Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, _last_platform_error :: proc() -> i32 {
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, return i32(_last_error)
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM,
Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT,
Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE,
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(_last_error)
Port_Required = -1, return description
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT,
Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED,
// `Refused` alias for `core:net` tests.
// The above default name `Refused_By_Remote_Host` is more explicit.
Refused = Refused_By_Remote_Host,
Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET,
Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH,
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY,
Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES,
Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
// NOTE: There are additional connect() error possibilities, but they are
// strictly for addresses in the UNIX domain.
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, _last_error = freebsd.Errno(err)
Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
// NOTE: bind() can also return EINVAL if the underlying `addrlen` is an
// invalid length for the address family. This shouldn't happen for the net
// package, but it's worth noting.
Already_Bound = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Protected_Address = cast(c.int)freebsd.Errno.EACCES,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
// NOTE: There are additional bind() error possibilities, but they are
// strictly for addresses in the UNIX domain.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ,
Already_Connected = cast(c.int)freebsd.Errno.EINVAL, #partial switch errno {
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Interrupted = cast(c.int)freebsd.Errno.EINTR,
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL,
Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT,
// NOTE: This is the same as EWOULDBLOCK. #partial switch errno {
No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN:
// `Would_Block` alias for `core:net` tests. return .Invalid_Argument
Would_Block = cast(c.int)freebsd.Errno.EAGAIN, case .EISCONN:
return .Already_Connected
New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
} }
TCP_Recv_Error :: enum c.int { _bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(), #partial switch errno {
// but I'm including them for completeness's sake. case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, return .Insufficient_Resources
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, case .EBADF:
return .Invalid_Argument
Timeout = cast(c.int)freebsd.Errno.EAGAIN, case .EINVAL:
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, return .Already_Bound
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(), #partial switch errno {
// but I'm including them for completeness's sake. case .EBADF, .ENOTSOCK:
Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, return .Invalid_Argument
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
Timeout = cast(c.int)freebsd.Errno.EAGAIN, case .EINVAL:
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, return .Already_Connected
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, case:
return .Unknown
}
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
None = 0, assert(errno != nil)
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, _last_error = errno
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE, #partial switch errno {
case .EMFILE, .ENFILE:
/* The socket is marked non-blocking, or MSG_DONTWAIT is return .Insufficient_Resources
specified, and the requested operation would block. */ case .EBADF, .ENOTSOCK, .EFAULT:
Would_Block = cast(c.int)freebsd.Errno.EAGAIN, return .Invalid_Argument
case .EINVAL:
/* NOTE: This error arises for two distinct reasons: return .Not_Listening
case .ECONNABORTED:
1. The system was unable to allocate an internal buffer. return .Aborted
The operation may succeed when buffers become available. case .EWOULDBLOCK:
return .Would_Block
2. The output queue for a network interface was full. case .EINTR:
This generally indicates that the interface has stopped return .Interrupted
sending, but may be caused by transient congestion. case:
*/ return .Unknown
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, }
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED,
Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN,
Network_Down = cast(c.int)freebsd.Errno.ENETDOWN,
Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE,
} }
// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. _tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
UDP_Send_Error :: distinct TCP_Send_Error assert(errno != nil)
_last_error = errno
Shutdown_Manner :: enum c.int { #partial switch errno {
Receive = cast(c.int)freebsd.Shutdown_Method.RD, case .EBADF, .ENOTSOCK, .EFAULT:
Send = cast(c.int)freebsd.Shutdown_Method.WR, return .Invalid_Argument
Both = cast(c.int)freebsd.Shutdown_Method.RDWR, case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, #partial switch errno {
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error {
None = 0, assert(errno != nil)
Value_Out_Of_Range = -1, _last_error = errno
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, #partial switch errno {
Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, return .Invalid_Argument
// This error can arise for many different reasons. case .ENOBUFS:
Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, return .Insufficient_Resources
System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, case .ECONNRESET, .EPIPE:
Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error {
None = 0, assert(errno != nil)
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, _last_error = errno
Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT:
return .Invalid_Option
case .EINVAL, .EFAULT:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTTY:
return .Invalid_Argument
case:
return .Unknown
}
} }
+234 -146
View File
@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import "core:sys/linux" import "core:sys/linux"
Create_Socket_Error :: enum c.int { @(private="file", thread_local)
None = 0, _last_error: linux.Errno
Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), _last_platform_error :: proc() -> i32 {
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), return i32(_last_error)
No_Memory_Available_Available = c.int(linux.Errno.ENOMEM),
Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT),
} }
Dial_Error :: enum c.int { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(_last_error)
Port_Required = -1, return description
Address_In_Use = c.int(linux.Errno.EADDRINUSE),
In_Progress = c.int(linux.Errno.EINPROGRESS),
Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT),
Refused = c.int(linux.Errno.ECONNREFUSED),
Is_Listening_Socket = c.int(linux.Errno.EACCES),
Already_Connected = c.int(linux.Errno.EISCONN),
Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Timeout = c.int(linux.Errno.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(linux.Errno.EWOULDBLOCK),
} }
Bind_Error :: enum c.int { _set_last_platform_error :: proc(err: i32) {
None = 0, _last_error = linux.Errno(err)
Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available.
} }
Listen_Error :: enum c.int { _create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
None = 0, assert(errno != nil)
Address_In_Use = c.int(linux.Errno.EADDRINUSE), _last_error = errno
Already_Connected = c.int(linux.Errno.EISCONN),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), #partial switch errno {
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), return .Insufficient_Resources
Not_Socket = c.int(linux.Errno.ENOTSOCK), case .EAFNOSUPPORT, .EPROTOTYPE:
Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP), return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc(errno: linux.Errno) -> Dial_Error {
None = 0, assert(errno != nil)
Not_Listening = c.int(linux.Errno.EINVAL), _last_error = errno
No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? #partial switch errno {
Would_Block = c.int(linux.Errno.EWOULDBLOCK), case .EAGAIN:
return .Insufficient_Resources
case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
} }
TCP_Recv_Error :: enum c.int { _bind_error :: proc(errno: linux.Errno) -> Bind_Error {
None = 0, assert(errno != nil)
Shutdown = c.int(linux.Errno.ESHUTDOWN), _last_error = errno
Not_Connected = c.int(linux.Errno.ENOTCONN),
Connection_Broken = c.int(linux.Errno.ENETRESET),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Aborted = c.int(linux.Errno.ECONNABORTED),
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? #partial switch errno {
Connection_Closed = c.int(linux.Errno.ECONNRESET), case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
Offline = c.int(linux.Errno.ENETDOWN), return .Insufficient_Resources
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), case .EINVAL:
Interrupted = c.int(linux.Errno.EINTR), return .Already_Bound
Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... case .EBADF:
return .Invalid_Argument
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
} }
UDP_Recv_Error :: enum c.int { _listen_error :: proc(errno: linux.Errno) -> Listen_Error {
None = 0, assert(errno != nil)
_last_error = errno
Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. #partial switch errno {
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. case .EBADF, .ENOTSOCK:
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Invalid_Argument
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. case .EDESTADDRREQ, .EOPNOTSUPP:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Unsupported_Socket
case .EINVAL:
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout. return .Already_Connected
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case:
Timeout = c.int(linux.Errno.EWOULDBLOCK), return .Unknown
Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't. }
} }
TCP_Send_Error :: enum c.int { _accept_error :: proc(errno: linux.Errno) -> Accept_Error {
None = 0, assert(errno != nil)
Aborted = c.int(linux.Errno.ECONNABORTED), _last_error = errno
Connection_Closed = c.int(linux.Errno.ECONNRESET),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Shutdown = c.int(linux.Errno.ESHUTDOWN),
// The send queue was full. #partial switch errno {
// This is usually a transient issue. case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
// return .Insufficient_Resources
// This also shouldn't normally happen on Linux, as data is dropped if it case .EBADF, .ENOTSOCK, .EFAULT:
// doesn't fit in the send queue. return .Invalid_Argument
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), case .EINVAL:
Offline = c.int(linux.Errno.ENETDOWN), return .Not_Listening
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), case .ECONNABORTED:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Aborted
Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. case .EWOULDBLOCK:
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
// TODO _tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
UDP_Send_Error :: enum c.int { assert(errno != nil)
None = 0, _last_error = errno
Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
// TODO: not sure what the exact circumstances for this is yet #partial switch errno {
Network_Unreachable = c.int(linux.Errno.ENETUNREACH), case .EBADF, .ENOTSOCK, .EFAULT:
No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. return .Invalid_Argument
case .ENOTCONN:
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. return .Not_Connected
// NOTE: No, really. Presumably this means something different for nonblocking sockets... case .ECONNREFUSED, .ECONNRESET:
Timeout = c.int(linux.Errno.EWOULDBLOCK), return .Connection_Closed
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. case .ETIMEDOUT:
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. return .Timeout
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. case .EAGAIN:
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). return .Would_Block
case .EINTR:
// The send queue was full. return .Interrupted
// This is usually a transient issue. case:
// return .Unknown
// 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(linux.Errno.ENOBUFS),
No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue.
} }
// TODO(flysand): slight regression _udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
Shutdown_Manner :: enum c.int { assert(errno != nil)
Receive = c.int(linux.Shutdown_How.RD), _last_error = errno
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR), #partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNREFUSED, .ENOTCONN, .ECONNRESET:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
None = 0, assert(errno != nil)
Aborted = c.int(linux.Errno.ECONNABORTED), _last_error = errno
Reset = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN), #partial switch errno {
Not_Connected = c.int(linux.Errno.ENOTCONN), case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
Not_Socket = c.int(linux.Errno.ENOTSOCK), return .Invalid_Argument
Invalid_Manner = c.int(linux.Errno.EINVAL), case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Socket_Option_Error :: enum c.int { _udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
None = 0, assert(errno != nil)
Offline = c.int(linux.Errno.ENETDOWN), _last_error = errno
Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT), #partial switch errno {
Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN), case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
Not_Socket = c.int(linux.Errno.ENOTSOCK), return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
} }
Set_Blocking_Error :: enum c.int { _shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
None = 0, assert(errno != nil)
_last_error = errno
// TODO: add errors occuring on followig calls: #partial switch errno {
// flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0) case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
// linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK)) return .Invalid_Argument
} case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT, .EINVAL:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
}
+20
View File
@@ -0,0 +1,20 @@
#+build !darwin
#+build !linux
#+build !freebsd
#+build !windows
package net
@(private="file", thread_local)
_last_error: i32
_last_platform_error :: proc() -> i32 {
return _last_error
}
_last_platform_error_string :: proc() -> string {
return ""
}
_set_last_platform_error :: proc(err: i32) {
_last_error = err
}
+210 -218
View File
@@ -20,250 +20,242 @@ package net
Feoramund: FreeBSD platform code Feoramund: FreeBSD platform code
*/ */
import "core:c" import "core:reflect"
import win "core:sys/windows" import win "core:sys/windows"
Create_Socket_Error :: enum c.int { _last_platform_error :: proc() -> i32 {
None = 0, return i32(win.WSAGetLastError())
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 { _last_platform_error_string :: proc() -> string {
None = 0, description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError()))
Port_Required = -1, return description
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 { _set_last_platform_error :: proc(err: i32) {
None = 0, win.WSASetLastError(err)
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 { _create_socket_error :: proc() -> Create_Socket_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Address_In_Use = win.WSAEADDRINUSE, case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT:
Already_Connected = win.WSAEISCONN, return .Network_Unreachable
No_Socket_Descriptors_Available = win.WSAEMFILE, case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Nonlocal_Address = win.WSAEADDRNOTAVAIL, case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT:
Not_Socket = win.WSAENOTSOCK, return .Insufficient_Resources
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, case:
return .Unknown
}
} }
Accept_Error :: enum c.int { _dial_error :: proc() -> Dial_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Not_Listening = win.WSAEINVAL, case .WSANOTINITIALISED, .WSAENETDOWN:
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, return .Network_Unreachable
No_Buffer_Space_Available = win.WSAENOBUFS, case .WSAEADDRINUSE:
Not_Socket = win.WSAENOTSOCK, return .Address_In_Use
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, case .WSAEINTR:
return .Interrupted
// TODO: we may need special handling for this; maybe make a socket a struct with metadata? case .WSAEWOULDBLOCK:
Would_Block = win.WSAEWOULDBLOCK, return .Would_Block
case .WSAEALREADY:
return .Already_Connecting
case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
return .Invalid_Argument
case .WSAECONNREFUSED:
return .Refused
case .WSAEISCONN:
return .Already_Connected
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAETIMEDOUT:
return .Timeout
case .WSAEACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
} }
TCP_Recv_Error :: enum c.int { _bind_error :: proc() -> Bind_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
Not_Connected = win.WSAENOTCONN, return .Network_Unreachable
Bad_Buffer = win.WSAEFAULT, case .WSAEADDRINUSE:
Keepalive_Failure = win.WSAENETRESET, return .Address_In_Use
Not_Socket = win.WSAENOTSOCK, case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK:
Shutdown = win.WSAESHUTDOWN, return .Invalid_Argument
Would_Block = win.WSAEWOULDBLOCK, case:
Aborted = win.WSAECONNABORTED, return .Unknown
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 { _listen_error :: proc() -> Listen_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
Aborted = win.WSAECONNABORTED, return .Network_Unreachable
Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. case .WSAEMFILE, .WSAENOBUFS:
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. return .Insufficient_Resources
Shutdown = win.WSAESHUTDOWN, case .WSAEADDRINUSE:
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set. return .Address_In_Use
Bad_Buffer = win.WSAEFAULT, case .WSAEINPROGRESS, .WSAENOTSOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. case .WSAEISCONN:
Would_Block = win.WSAEWOULDBLOCK, return .Already_Connected
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. case .WSAEOPNOTSUPP, .WSAEINVAL:
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. return .Unsupported_Socket
Timeout = win.WSAETIMEDOUT, case:
return .Unknown
// 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 _accept_error :: proc() -> Accept_Error {
// TODO: verify once more what errors to actually expose #partial switch win.System_Error(win.WSAGetLastError()) {
TCP_Send_Error :: enum c.int { case .WSANOTINITIALISED, .WSAENETDOWN:
None = 0, return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
Aborted = win.WSAECONNABORTED, return .Insufficient_Resources
Not_Connected = win.WSAENOTCONN, case .WSAECONNRESET:
Shutdown = win.WSAESHUTDOWN, return .Aborted
Connection_Closed = win.WSAECONNRESET, case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Invalid_Argument
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSAEINTR:
Host_Unreachable = win.WSAEHOSTUNREACH, return .Interrupted
Would_Block = win.WSAEWOULDBLOCK, case .WSAEINVAL:
return .Not_Listening
// TODO: verify possible, as not mentioned in docs case .WSAEWOULDBLOCK:
Offline = win.WSAENETUNREACH, return .Would_Block
Timeout = win.WSAETIMEDOUT, case .WSAEOPNOTSUPP:
return .Unsupported_Socket
// A broadcast address was specified, but the .Broadcast socket option isn't set. case:
Broadcast_Disabled = win.WSAEACCES, return .Unknown
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 { _tcp_recv_error :: proc() -> TCP_Recv_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Network_Subsystem_Failure = win.WSAENETDOWN, case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
Aborted = win.WSAECONNABORTED, case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP:
Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size. return .Invalid_Argument
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. case .WSAENOTCONN:
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set. return .Not_Connected
Broadcast_Disabled = win.WSAEACCES, case .WSAEINTR:
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation. return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
// TODO: not functionally different from Reset; merge? return .Connection_Closed
Keepalive_Failure = win.WSAENETRESET, case .WSAEWOULDBLOCK:
No_Buffer_Space_Available = win.WSAENOBUFS, return .Would_Block
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. case .WSAETIMEDOUT:
return .Timeout
// This socket is unidirectional and cannot be used to send any data. case:
// TODO: verify possible; decide whether to keep if not return .Unknown
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 { _udp_recv_error :: proc() -> UDP_Recv_Error {
Receive = win.SD_RECEIVE, #partial switch win.System_Error(win.WSAGetLastError()) {
Send = win.SD_SEND, case .WSANOTINITIALISED, .WSAENETDOWN:
Both = win.SD_BOTH, return .Network_Unreachable
case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
return .Connection_Refused
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
} }
Shutdown_Error :: enum c.int { _tcp_send_error :: proc() -> TCP_Send_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Aborted = win.WSAECONNABORTED, case .WSANOTINITIALISED, .WSAENETDOWN:
Reset = win.WSAECONNRESET, return .Network_Unreachable
Offline = win.WSAENETDOWN, case .WSAENOBUFS:
Not_Connected = win.WSAENOTCONN, return .Insufficient_Resources
Not_Socket = win.WSAENOTSOCK, case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL:
Invalid_Manner = win.WSAEINVAL, return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAENOTCONN:
return .Not_Connected
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case:
return .Unknown
}
} }
Socket_Option :: enum c.int { _udp_send_error :: proc() -> UDP_Send_Error {
// bool: Whether the address that this socket is bound to can be reused by other sockets. #partial switch win.System_Error(win.WSAGetLastError()) {
// This allows you to bypass the cooldown period if a program dies while the socket is bound. case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH:
Reuse_Address = win.SO_REUSEADDR, return .Network_Unreachable
case .WSAENOBUFS:
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket. return .Insufficient_Resources
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ:
return .Invalid_Argument
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding case .WSAEINTR:
Keep_Alive = win.SO_KEEPALIVE, return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. return .Connection_Refused
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, case .WSAEWOULDBLOCK:
return .Would_Block
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. case .WSAETIMEDOUT:
Dont_Linger = win.SO_DONTLINGER, return .Timeout
case:
// 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. return .Unknown
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 { _shutdown_error :: proc() -> Shutdown_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
Linger_Only_Supports_Whole_Seconds = 1, case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
// The given value is too big or small to be given to the OS. case .WSAECONNABORTED, .WSAECONNRESET:
Value_Out_Of_Range, return .Connection_Closed
case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK:
Network_Subsystem_Failure = win.WSAENETDOWN, return .Invalid_Argument
Timeout_When_Keepalive_Set = win.WSAENETRESET, case:
Invalid_Option_For_Socket = win.WSAENOPROTOOPT, return .Unknown
Reset_When_Keepalive_Set = win.WSAENOTCONN, }
Not_Socket = win.WSAENOTSOCK,
} }
Set_Blocking_Error :: enum c.int { _socket_option_error :: proc() -> Socket_Option_Error {
None = 0, #partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINVAL:
return .Invalid_Value
case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Socket
case .WSAENOPROTOOPT:
return .Invalid_Option
case:
return .Unknown
}
}
Network_Subsystem_Failure = win.WSAENETDOWN, _set_blocking_error :: proc() -> Set_Blocking_Error {
Blocking_Call_In_Progress = win.WSAEINPROGRESS, #partial switch win.System_Error(win.WSAGetLastError()) {
Not_Socket = win.WSAENOTSOCK, case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
// TODO: are those errors possible? case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT:
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED, return .Invalid_Argument
Invalid_Argument_Pointer = win.WSAEFAULT, case:
} return .Unknown
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3
/* /*
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties. `enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
*/ */
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
return _enumerate_interfaces(allocator) return _enumerate_interfaces(allocator)
} }
+2 -2
View File
@@ -26,7 +26,7 @@ import "core:sys/posix"
foreign import lib "system:System.framework" foreign import lib "system:System.framework"
@(private) @(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator context.allocator = allocator
head: ^ifaddrs head: ^ifaddrs
@@ -47,7 +47,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
iface.adapter_name = key_ptr^ iface.adapter_name = key_ptr^
} }
if mem_err != nil { if mem_err != nil {
return {}, .Unable_To_Enumerate_Network_Interfaces return {}, .Allocation_Failure
} }
address: Address address: Address
+2 -2
View File
@@ -25,7 +25,7 @@ import "core:strings"
import "core:sys/freebsd" import "core:sys/freebsd"
@(private) @(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
// This is a simplified implementation of `getifaddrs` from the FreeBSD // This is a simplified implementation of `getifaddrs` from the FreeBSD
// libc using only Odin and syscalls. // libc using only Odin and syscalls.
context.allocator = allocator context.allocator = allocator
@@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// Allocate and get the entries. // Allocate and get the entries.
buf, alloc_err := make([]byte, needed) buf, alloc_err := make([]byte, needed)
if alloc_err != nil { if alloc_err != nil {
return nil, .Unable_To_Enumerate_Network_Interfaces return nil, .Allocation_Failure
} }
defer delete(buf) defer delete(buf)
+2 -2
View File
@@ -30,7 +30,7 @@ package net
// NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html // NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html
// apparently musl libc uses this to enumerate network interfaces // apparently musl libc uses this to enumerate network interfaces
@(private) @(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator context.allocator = allocator
// head: ^os.ifaddrs // head: ^os.ifaddrs
@@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// } // }
// return _interfaces[:], {} // return _interfaces[:], {}
return nil, {} return nil, {}
} }
+7 -6
View File
@@ -23,7 +23,7 @@ package net
import sys "core:sys/windows" import sys "core:sys/windows"
import strings "core:strings" import strings "core:strings"
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator context.allocator = allocator
buf: []u8 buf: []u8
@@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
case 0: case 0:
break gaa break gaa
case: case:
return {}, Platform_Error(res) set_last_platform_error(i32(res))
return {}, .Unknown
} }
} }
@@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
_interfaces := make([dynamic]Network_Interface, 0, allocator) _interfaces := make([dynamic]Network_Interface, 0, allocator)
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { 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) friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
if err1 != nil { return {}, Platform_Error(err1) } if err1 != nil { return {}, .Allocation_Failure }
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator) description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
if err2 != nil { return {}, Platform_Error(err2) } if err2 != nil { return {}, .Allocation_Failure }
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator) dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
if err3 != nil { return {}, Platform_Error(err3) } if err3 != nil { return {}, .Allocation_Failure }
interface := Network_Interface{ interface := Network_Interface{
adapter_name = strings.clone(string(adapter.AdapterName)), adapter_name = strings.clone(string(adapter.AdapterName)),
@@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
case: return // Empty or invalid address type case: return // Empty or invalid address type
} }
unreachable() unreachable()
} }
+50 -19
View File
@@ -35,8 +35,10 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent. `a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`. Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/ */
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { 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 target := parse_hostname_or_endpoint(hostname_and_port) or_return
return dial_tcp_from_host_or_endpoint(target, options) return dial_tcp_from_host_or_endpoint(target, options)
@@ -47,8 +49,10 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP. `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. If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/ */
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { 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 target := parse_hostname_or_endpoint(hostname) or_return
switch &t in target { switch &t in target {
case Endpoint: case Endpoint:
@@ -62,8 +66,10 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o
/* /*
Expects the `host` as Host. Expects the `host` as Host.
Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/ */
dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { dial_tcp_from_host :: proc(host: Host, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if host.port == 0 { if host.port == 0 {
return 0, .Port_Required return 0, .Port_Required
} }
@@ -76,8 +82,10 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke
/* /*
Expects the `target` as a Host_OrEndpoint. Expects the `target` as a Host_OrEndpoint.
Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`. Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`.
Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/ */
dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
switch t in target { switch t in target {
case Endpoint: case Endpoint:
return dial_tcp_from_endpoint(t, options) return dial_tcp_from_endpoint(t, options)
@@ -87,12 +95,21 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa
unreachable() 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) { Dial from an Address.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
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) 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) { /*
Dial from an Endpoint.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
return _dial_tcp_from_endpoint(endpoint, options) return _dial_tcp_from_endpoint(endpoint, options)
} }
@@ -105,11 +122,11 @@ dial_tcp :: proc{
dial_tcp_from_host_or_endpoint, dial_tcp_from_host_or_endpoint,
} }
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
return _create_socket(family, protocol) return _create_socket(family, protocol)
} }
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
return _bind(socket, ep) return _bind(socket, ep)
} }
@@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. 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) { make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) {
sock := create_socket(family, .UDP) or_return sock := create_socket(family, .UDP) or_return
socket = sock.(UDP_Socket) socket = sock.(UDP_Socket)
return return
@@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket,
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection 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. 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.
Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error`
*/ */
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) { make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
if bound_address == nil { if bound_address == nil {
@@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP
return return
} }
/*
Creates a TCP socket and starts listening on the given endpoint.
Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error`
*/
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && backlog < int(max(i32))) assert(backlog > 0 && backlog < int(max(i32)))
@@ -150,11 +174,11 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/* /*
Returns the endpoint that the given socket is listening / bound on. Returns the endpoint that the given socket is listening / bound on.
*/ */
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) {
return _bound_endpoint(socket) return _bound_endpoint(socket)
} }
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options) return _accept_tcp(socket, options)
} }
@@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) {
_close(socket) _close(socket)
} }
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
return _recv_tcp(socket, buf) return _recv_tcp(socket, buf)
} }
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
return _recv_udp(socket, buf) return _recv_udp(socket, buf)
} }
@@ -175,6 +199,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en
Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it
will always return `nil`. will always return `nil`.
Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`
*/ */
recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( recv_any :: proc(socket: Any_Socket, buf: []byte) -> (
bytes_read: int, bytes_read: int,
@@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any}
Repeatedly sends data until the entire buffer is sent. 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. 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) { send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
return _send_tcp(socket, buf) return _send_tcp(socket, buf)
} }
@@ -207,10 +233,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. 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. 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) { send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
return _send_udp(socket, buf, to) return _send_udp(socket, buf, to)
} }
/*
Sends data over the socket.
Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error`
*/
send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> ( send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> (
bytes_written: int, bytes_written: int,
err: Network_Error, err: Network_Error,
@@ -226,14 +257,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) ->
send :: proc{send_tcp, send_udp, send_any} send :: proc{send_tcp, send_udp, send_any}
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
return _shutdown(socket, manner) return _shutdown(socket, manner)
} }
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
return _set_option(socket, option, value, loc) return _set_option(socket, option, value, loc)
} }
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
return _set_blocking(socket, should_block) return _set_blocking(socket, should_block)
} }
+33 -52
View File
@@ -37,8 +37,14 @@ Socket_Option :: enum c.int {
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO), Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO),
} }
Shutdown_Manner :: enum c.int {
Receive = c.int(posix.SHUT_RD),
Send = c.int(posix.SHUT_WR),
Both = c.int(posix.SHUT_RDWR),
}
@(private) @(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type: posix.Sock c_type: posix.Sock
c_protocol: posix.Protocol c_protocol: posix.Protocol
c_family: posix.AF c_family: posix.AF
@@ -59,7 +65,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := posix.socket(c_family, c_type, c_protocol) sock := posix.socket(c_family, c_type, c_protocol)
if sock < 0 { if sock < 0 {
err = Create_Socket_Error(posix.errno()) err = _create_socket_error()
return return
} }
@@ -72,7 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
} }
@(private) @(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (skt: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 { if endpoint.port == 0 {
return 0, .Port_Required return 0, .Port_Required
} }
@@ -88,28 +94,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno() err = _dial_error()
close(skt) close(skt)
return {}, Dial_Error(errno)
} }
return return
} }
// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it.
MAX_PRIVILEGED_PORT :: 1023
@(private) @(private)
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno() err = _bind_error()
if errno == .EACCES && ep.port <= MAX_PRIVILEGED_PORT {
err = .Privileged_Port_Without_Root
} else {
err = Bind_Error(errno)
}
} }
return return
@@ -128,25 +125,23 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
// bypass the cooldown period, and allow the next run of the program to // bypass the cooldown period, and allow the next run of the program to
// use the same address immediately. // 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)
set_option(sock, .Reuse_Address, true) or_return
bind(sock, interface_endpoint) or_return bind(sock, interface_endpoint) or_return
if posix.listen(posix.FD(skt), i32(backlog)) != .OK { if posix.listen(posix.FD(skt), i32(backlog)) != .OK {
err = Listen_Error(posix.errno()) err = _listen_error()
return
} }
return return
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: posix.sockaddr_storage addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = Listen_Error(posix.errno()) err = _listen_error()
return return
} }
@@ -155,12 +150,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { _accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
addr: posix.sockaddr_storage addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len) client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len)
if client_sock < 0 { if client_sock < 0 {
err = Accept_Error(posix.errno()) err = _accept_error()
return return
} }
@@ -176,14 +171,14 @@ _close :: proc(skt: Any_Socket) {
} }
@(private) @(private)
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
return return
} }
res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {}) res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {})
if res < 0 { if res < 0 {
err = TCP_Recv_Error(posix.errno()) err = _tcp_recv_error()
return return
} }
@@ -191,7 +186,7 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
} }
@(private) @(private)
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
return return
} }
@@ -200,7 +195,7 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
fromsize := posix.socklen_t(size_of(from)) fromsize := posix.socklen_t(size_of(from))
res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize) res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize)
if res < 0 { if res < 0 {
err = UDP_Recv_Error(posix.errno()) err = _udp_recv_error()
return return
} }
@@ -210,20 +205,13 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
} }
@(private) @(private)
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}) res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL})
if res < 0 { if res < 0 {
errno := posix.errno() err = _tcp_send_error()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = TCP_Send_Error.Connection_Closed
return
}
err = TCP_Send_Error(errno)
return return
} }
@@ -233,21 +221,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
} }
@(private) @(private)
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written) limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len)) res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len))
if res < 0 { if res < 0 {
errno := posix.errno() err = _udp_send_error()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = UDP_Send_Error.Not_Socket
return
}
err = UDP_Send_Error(errno)
return return
} }
@@ -257,16 +238,16 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
} }
@(private) @(private)
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { _shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK { if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK {
err = Shutdown_Error(posix.errno()) err = _shutdown_error()
} }
return return
} }
@(private) @(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
@@ -337,19 +318,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
skt := any_socket_to_socket(s) skt := any_socket_to_socket(s)
if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK { if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK {
return Socket_Option_Error(posix.errno()) return _socket_option_error()
} }
return nil return nil
} }
@(private) @(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket) socket := any_socket_to_socket(socket)
flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0) flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0)
if flags_ < 0 { if flags_ < 0 {
return Set_Blocking_Error(posix.errno()) return _set_blocking_error()
} }
flags := transmute(posix.O_Flags)flags_ flags := transmute(posix.O_Flags)flags_
@@ -360,7 +341,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
} }
if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 { if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 {
return Set_Blocking_Error(posix.errno()) return _set_blocking_error()
} }
return nil return nil
+41 -52
View File
@@ -54,8 +54,14 @@ Socket_Option :: enum c.int {
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
} }
Shutdown_Manner :: enum c.int {
Receive = cast(c.int)freebsd.Shutdown_Method.RD,
Send = cast(c.int)freebsd.Shutdown_Method.WR,
Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
}
@(private) @(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
sys_family: freebsd.Protocol_Family = --- sys_family: freebsd.Protocol_Family = ---
sys_protocol: freebsd.Protocol = --- sys_protocol: freebsd.Protocol = ---
sys_socket_type: freebsd.Socket_Type = --- sys_socket_type: freebsd.Socket_Type = ---
@@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol) new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
if errno != nil { if errno != nil {
err = cast(Create_Socket_Error)errno err = _create_socket_error(errno)
return
}
// NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE
// error is raised during the writing of a socket that may be closed.
// This behavior is unlikely to be expected by general users.
//
// There are two workarounds. One is to apply the .NOSIGNAL flag when using
// the `sendto` syscall. However, that would prevent users of this library
// from re-enabling the SIGPIPE-raising functionality, if they really
// wanted it.
//
// So I have disabled it here with this socket option for all sockets.
truth: b32 = true
errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth))
if errno != nil {
err = cast(Socket_Option_Error)errno
return return
} }
@@ -102,7 +91,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
} }
@(private) @(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 { if endpoint.port == 0 {
return 0, .Port_Required return 0, .Port_Required
} }
@@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil { if errno != nil {
close(socket) close(socket)
return {}, cast(Dial_Error)errno return {}, _dial_error(errno)
} }
return return
} }
@(private) @(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { _bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
real_socket := any_socket_to_socket(socket) real_socket := any_socket_to_socket(socket)
errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil { if errno != nil {
err = cast(Bind_Error)errno err = _bind_error(errno)
} }
return return
} }
@@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
errno := freebsd.listen(cast(Fd)socket, backlog) errno := freebsd.listen(cast(Fd)socket, backlog)
if errno != nil { if errno != nil {
err = cast(Listen_Error)errno err = _listen_error(errno)
return return
} }
@@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: freebsd.Socket_Address_Storage sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil { if errno != nil {
err = cast(Listen_Error)errno err = _listen_error(errno)
return return
} }
@@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { _accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
sockaddr: freebsd.Socket_Address_Storage sockaddr: freebsd.Socket_Address_Storage
result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
if errno != nil { if errno != nil {
err = cast(Accept_Error)errno err = _accept_error(errno)
return return
} }
@@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) {
} }
@(private) @(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { _recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) == 0 { if len(buf) == 0 {
return return
} }
result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
if errno != nil { if errno != nil {
err = cast(TCP_Recv_Error)errno err = _tcp_recv_error(errno)
return return
} }
return result, nil return result, nil
} }
@(private) @(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) == 0 { if len(buf) == 0 {
return return
} }
@@ -208,21 +197,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from) result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
if errno != nil { if errno != nil {
err = cast(UDP_Recv_Error)errno err = _udp_recv_error(errno)
return return
} }
return result, _sockaddr_to_endpoint(&from), nil return result, _sockaddr_to_endpoint(&from), nil
} }
@(private) @(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE) result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL)
if errno != nil { if errno != nil {
err = cast(TCP_Send_Error)errno err = _tcp_send_error(errno)
return return
} }
bytes_written += result bytes_written += result
@@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
} }
@(private) @(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr) result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr)
if errno != nil { if errno != nil {
err = cast(UDP_Send_Error)errno err = _udp_send_error(errno)
return return
} }
bytes_written += result bytes_written += result
@@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt
} }
@(private) @(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { _shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
real_socket := cast(Fd)any_socket_to_socket(socket) real_socket := cast(Fd)any_socket_to_socket(socket)
errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner)
if errno != nil { if errno != nil {
return cast(Shutdown_Error)errno return _shutdown_error(errno)
} }
return return
} }
@(private) @(private)
_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
// NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32 // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32
// bits for a boolean socket option value. Nothing less will work. // bits for a boolean socket option value. Nothing less will work.
bool_value: b32 bool_value: b32
@@ -315,25 +304,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
case u16: int_value = cast(i32)real case u16: int_value = cast(i32)real
case i32: int_value = real case i32: int_value = real
case u32: case u32:
if real > u32(max(i32)) { return .Value_Out_Of_Range } if real > u32(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case i64: case i64:
if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range } if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case u64: case u64:
if real > u64(max(i32)) { return .Value_Out_Of_Range } if real > u64(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case i128: case i128:
if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range } if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case u128: case u128:
if real > u128(max(i32)) { return .Value_Out_Of_Range } if real > u128(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case int: case int:
if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range } if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case uint: case uint:
if real > uint(max(i32)) { return .Value_Out_Of_Range } if real > uint(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real int_value = cast(i32)real
case: case:
panic("set_option() value must be an integer here", loc) panic("set_option() value must be an integer here", loc)
@@ -347,19 +336,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
real_socket := any_socket_to_socket(socket) real_socket := any_socket_to_socket(socket)
errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len)
if errno != nil { if errno != nil {
return cast(Socket_Option_Error)errno return _socket_option_error(errno)
} }
return nil return nil
} }
@(private) @(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
real_socket := any_socket_to_socket(socket) real_socket := any_socket_to_socket(socket)
flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket)
if errno != nil { if errno != nil {
return cast(Set_Blocking_Error)errno return _set_blocking_error(errno)
} }
if should_block { if should_block {
@@ -370,7 +359,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags) errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
if errno != nil { if errno != nil {
return cast(Set_Blocking_Error)errno return _set_blocking_error(errno)
} }
return return
+44 -43
View File
@@ -38,15 +38,21 @@ Socket_Option :: enum c.int {
Broadcast = c.int(linux.Socket_Option.BROADCAST), Broadcast = c.int(linux.Socket_Option.BROADCAST),
} }
Shutdown_Manner :: enum c.int {
Receive = c.int(linux.Shutdown_How.RD),
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR),
}
// Wrappers and unwrappers for system-native types // Wrappers and unwrappers for system-native types
@(private="file") @(private="file")
_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd { _unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd {
return linux.Fd(any_socket_to_socket(sock)) return linux.Fd(any_socket_to_socket(sock))
} }
@(private="file") @(private="file")
_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket { _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket {
switch protocol { switch protocol {
case .TCP: return TCP_Socket(Socket(sock)) case .TCP: return TCP_Socket(Socket(sock))
case .UDP: return UDP_Socket(Socket(sock)) case .UDP: return UDP_Socket(Socket(sock))
@@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol
} }
@(private="file") @(private="file")
_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family { _unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family {
switch family { switch family {
case .IP4: return .INET case .IP4: return .INET
case .IP6: return .INET6 case .IP6: return .INET6
@@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_
} }
@(private="file") @(private="file")
_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) { _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) {
switch protocol { switch protocol {
case .TCP: return .TCP, .STREAM case .TCP: return .TCP, .STREAM
case .UDP: return .UDP, .DGRAM case .UDP: return .UDP, .DGRAM
@@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li
} }
@(private="file") @(private="file")
_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) { _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any {
switch address in endpoint.address { switch address in endpoint.address {
case IP4_Address: case IP4_Address:
return { return {
@@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any
} }
@(private="file") @(private="file")
_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint {
#partial switch addr.family { #partial switch addr.family {
case .INET: case .INET:
return { return {
@@ -117,18 +123,18 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
} }
} }
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) {
family := _unwrap_os_family(family) family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol) proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE { if errno != .NONE {
return {}, Create_Socket_Error(errno) return {}, _create_socket_error(errno)
} }
return _wrap_os_socket(sock, protocol), nil return _wrap_os_socket(sock, protocol), nil
} }
@(private) @(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (TCP_Socket, Network_Error) { _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (TCP_Socket, Network_Error) {
errno: linux.Errno errno: linux.Errno
if endpoint.port == 0 { if endpoint.port == 0 {
return 0, .Port_Required return 0, .Port_Required
@@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno) return {}, _create_socket_error(errno)
} }
// NOTE(tetra): This is so that if we crash while the socket is open, we can // 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 // bypass the cooldown period, and allow the next run of the program to
@@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno = linux.connect(linux.Fd(os_sock), &addr) errno = linux.connect(linux.Fd(os_sock), &addr)
if errno != .NONE { if errno != .NONE {
close(cast(TCP_Socket) os_sock) close(cast(TCP_Socket) os_sock)
return {}, Dial_Error(errno) return {}, _dial_error(errno)
} }
// NOTE(tetra): Not vital to succeed; error ignored // NOTE(tetra): Not vital to succeed; error ignored
no_delay: b32 = cast(b32) options.no_delay no_delay: b32 = cast(b32) options.no_delay
@@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
} }
@(private) @(private)
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { _bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) {
addr := _unwrap_os_addr(endpoint) addr := _unwrap_os_addr(endpoint)
errno := linux.bind(_unwrap_os_socket(sock), &addr) errno := linux.bind(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
return Bind_Error(errno) return _bind_error(errno)
} }
return nil return nil
} }
@@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
os_sock: linux.Fd os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE { if errno != .NONE {
err = Create_Socket_Error(errno) err = _create_socket_error(errno)
return return
} }
socket = cast(TCP_Socket)os_sock socket = cast(TCP_Socket)os_sock
@@ -193,31 +199,30 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
do_reuse_addr: b32 = true do_reuse_addr: b32 = true
if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE {
err = Listen_Error(errno) err = _listen_error(errno)
return return
} }
// Bind the socket to endpoint address // Bind the socket to endpoint address
if errno = linux.bind(os_sock, &ep_address); errno != .NONE { if errno = linux.bind(os_sock, &ep_address); errno != .NONE {
err = Bind_Error(errno) err = _bind_error(errno)
return return
} }
// Listen on bound socket // Listen on bound socket
if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE {
err = Listen_Error(errno) err = _listen_error(errno)
return
} }
return return
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: linux.Sock_Addr_Any addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr) errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
err = Listen_Error(errno) err = _listen_error(errno)
return return
} }
@@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) { _accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) {
addr: linux.Sock_Addr_Any addr: linux.Sock_Addr_Any
client_sock, errno := linux.accept(linux.Fd(sock), &addr) client_sock, errno := linux.accept(linux.Fd(sock), &addr)
if errno != .NONE { if errno != .NONE {
return {}, {}, Accept_Error(errno) return {}, {}, _accept_error(errno)
} }
// NOTE(tetra): Not vital to succeed; error ignored // NOTE(tetra): Not vital to succeed; error ignored
val: b32 = cast(b32) options.no_delay val: b32 = cast(b32) options.no_delay
@@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) {
} }
@(private) @(private)
_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { _recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
return 0, nil return 0, nil
} }
bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {}) bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {})
if errno != .NONE { if errno != .NONE {
return 0, TCP_Recv_Error(errno) return 0, _tcp_recv_error(errno)
} }
return int(bytes_read), nil return int(bytes_read), nil
} }
@(private) @(private)
_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) { _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
// NOTE(flysand): It was returning no error, I didn't change anything // NOTE(flysand): It was returning no error, I didn't change anything
return 0, {}, {} return 0, {}, {}
@@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_
from_addr: linux.Sock_Addr_Any from_addr: linux.Sock_Addr_Any
bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr) bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr)
if errno != .NONE { if errno != .NONE {
return 0, {}, UDP_Recv_Error(errno) return 0, {}, _udp_recv_error(errno)
} }
if bytes_read > len(buf) { if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message. // NOTE(tetra): The buffer has been filled, with a partial message.
return len(buf), {}, .Buffer_Too_Small return len(buf), {}, .Excess_Truncated
} }
return bytes_read, _wrap_os_addr(from_addr), nil return bytes_read, _wrap_os_addr(from_addr), nil
} }
@(private) @(private)
_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) {
total_written := 0 total_written := 0
for total_written < len(buf) { for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written) limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit] remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE { if errno != .NONE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error, return total_written, _tcp_send_error(errno)
// so we turn that into a clearer error
return total_written, TCP_Send_Error.Connection_Closed
} else if errno != .NONE {
return total_written, TCP_Send_Error(errno)
} }
total_written += int(res) total_written += int(res)
} }
@@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
} }
@(private) @(private)
_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) { _send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) {
to_addr := _unwrap_os_addr(to) to_addr := _unwrap_os_addr(to)
bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr) bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr)
if errno != .NONE { if errno != .NONE {
return bytes_written, UDP_Send_Error(errno) return bytes_written, _udp_send_error(errno)
} }
return int(bytes_written), nil return int(bytes_written), nil
} }
@(private) @(private)
_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { _shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
os_sock := _unwrap_os_socket(sock) os_sock := _unwrap_os_socket(sock)
errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner) errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner)
if errno != .NONE { if errno != .NONE {
return Shutdown_Error(errno) return _shutdown_error(errno)
} }
return nil return nil
} }
// TODO(flysand): Figure out what we want to do with this on core:sys/ level. // TODO(flysand): Figure out what we want to do with this on core:sys/ level.
@(private) @(private)
_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level: int level: int
if option == .TCP_Nodelay { if option == .TCP_Nodelay {
level = int(linux.SOL_TCP) level = int(linux.SOL_TCP)
@@ -388,19 +389,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
errno = linux.setsockopt(os_sock, level, int(option), &int_value) errno = linux.setsockopt(os_sock, level, int(option), &int_value)
} }
if errno != .NONE { if errno != .NONE {
return Socket_Option_Error(errno) return _socket_option_error(errno)
} }
return nil return nil
} }
@(private) @(private)
_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) { _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
errno: linux.Errno errno: linux.Errno
flags: linux.Open_Flags flags: linux.Open_Flags
os_sock := _unwrap_os_socket(sock) os_sock := _unwrap_os_socket(sock)
flags, errno = linux.fcntl(os_sock, linux.F_GETFL) flags, errno = linux.fcntl(os_sock, linux.F_GETFL)
if errno != .NONE { if errno != .NONE {
return Set_Blocking_Error(errno) return _set_blocking_error(errno)
} }
if should_block { if should_block {
flags -= {.NONBLOCK} flags -= {.NONBLOCK}
@@ -409,7 +410,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err
} }
errno = linux.fcntl(os_sock, linux.F_SETFL, flags) errno = linux.fcntl(os_sock, linux.F_SETFL, flags)
if errno != .NONE { if errno != .NONE {
return Set_Blocking_Error(errno) return _set_blocking_error(errno)
} }
return nil return nil
} }
+89 -38
View File
@@ -24,13 +24,67 @@ import "core:c"
import win "core:sys/windows" import win "core:sys/windows"
import "core:time" import "core:time"
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,
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
}
@(init, private) @(init, private)
ensure_winsock_initialized :: proc() { ensure_winsock_initialized :: proc() {
win.ensure_winsock_initialized() win.ensure_winsock_initialized()
} }
@(private) @(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type, c_protocol, c_family: c.int c_type, c_protocol, c_family: c.int
switch family { switch family {
@@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := win.socket(c_family, c_type, c_protocol) sock := win.socket(c_family, c_type, c_protocol)
if sock == win.INVALID_SOCKET { if sock == win.INVALID_SOCKET {
err = Create_Socket_Error(win.WSAGetLastError()) err = _create_socket_error()
return return
} }
@@ -62,7 +116,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
} }
@(private) @(private)
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 { if endpoint.port == 0 {
err = .Port_Required err = .Port_Required
return return
@@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
err = Dial_Error(win.WSAGetLastError()) err = _dial_error()
close(socket) close(socket)
return {}, err return {}, err
} }
@@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
} }
@(private) @(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { _bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
sock := any_socket_to_socket(socket) sock := any_socket_to_socket(socket)
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr)) res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
err = Bind_Error(win.WSAGetLastError()) err = _bind_error()
} }
return return
} }
@@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
bind(sock, interface_endpoint) or_return bind(sock, interface_endpoint) or_return
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError()) err = _listen_error()
} }
return return
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError()) err = _listen_error()
return return
} }
@@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
} }
@(private) @(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { _accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
for { for {
sockaddr: win.SOCKADDR_STORAGE_LH sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) sockaddrlen := c.int(size_of(sockaddr))
@@ -150,7 +204,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
// can do this to match the behaviour. // can do this to match the behaviour.
continue continue
} }
err = Accept_Error(e) err = _accept_error()
return return
} }
client = TCP_Socket(client_sock) client = TCP_Socket(client_sock)
@@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) {
} }
@(private) @(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { _recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
return return
} }
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0) res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
if res < 0 { if res < 0 {
err = TCP_Recv_Error(win.WSAGetLastError()) err = _tcp_recv_error()
return return
} }
return int(res), nil return int(res), nil
} }
@(private) @(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 { if len(buf) <= 0 {
return return
} }
@@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
fromsize := c.int(size_of(from)) fromsize := c.int(size_of(from))
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
if res < 0 { if res < 0 {
err = UDP_Recv_Error(win.WSAGetLastError()) err = _udp_recv_error()
return return
} }
@@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
} }
@(private) @(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written) limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:] remaining := buf[bytes_written:]
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0) res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
if res < 0 { if res < 0 {
err = TCP_Send_Error(win.WSAGetLastError()) err = _tcp_send_error()
return return
} }
bytes_written += int(res) bytes_written += int(res)
@@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
} }
@(private) @(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_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) toaddr := _endpoint_to_sockaddr(to)
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) for bytes_written < len(buf) {
if res < 0 { limit := min(int(max(i32)), len(buf) - bytes_written)
err = UDP_Send_Error(win.WSAGetLastError()) remaining := buf[bytes_written:]
return res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr))
if res < 0 {
err = _udp_send_error()
return
}
bytes_written += int(res)
} }
bytes_written = int(res)
return return
} }
@(private) @(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { _shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(socket) s := any_socket_to_socket(socket)
res := win.shutdown(win.SOCKET(s), c.int(manner)) res := win.shutdown(win.SOCKET(s), c.int(manner))
if res < 0 { if res < 0 {
return Shutdown_Error(win.WSAGetLastError()) return _shutdown_error()
} }
return return
} }
@(private) @(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
bool_value: b32 bool_value: b32
@@ -283,11 +337,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
num_secs := i64(time.duration_seconds(t)) num_secs := i64(time.duration_seconds(t))
if time.Duration(num_secs * 1e9) != t {
return .Linger_Only_Supports_Whole_Seconds
}
if num_secs > i64(max(u16)) { if num_secs > i64(max(u16)) {
return .Value_Out_Of_Range return .Invalid_Value
} }
linger_value.l_onoff = 1 linger_value.l_onoff = 1
linger_value.l_linger = c.ushort(num_secs) linger_value.l_linger = c.ushort(num_secs)
@@ -323,19 +374,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
socket := any_socket_to_socket(s) socket := any_socket_to_socket(s)
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len) res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
if res < 0 { if res < 0 {
return Socket_Option_Error(win.WSAGetLastError()) return _socket_option_error()
} }
return nil return nil
} }
@(private) @(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket) socket := any_socket_to_socket(socket)
arg: win.DWORD = 0 if should_block else 1 arg: win.DWORD = 0 if should_block else 1
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg) res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
if res == win.SOCKET_ERROR { if res == win.SOCKET_ERROR {
return Set_Blocking_Error(win.WSAGetLastError()) return _set_blocking_error()
} }
return nil return nil
+5 -5
View File
@@ -180,7 +180,7 @@ Type_Kind :: enum u32le {
Struct = 10, Struct = 10,
Union = 11, Union = 11,
Enum = 12, Enum = 12,
Tuple = 13, Parameters = 13,
Proc = 14, Proc = 14,
Bit_Set = 15, Bit_Set = 15,
Simd_Vector = 16, Simd_Vector = 16,
@@ -256,10 +256,10 @@ Type :: struct {
types: Array(Type_Index), types: Array(Type_Index),
// Used by: // Used by:
// .Named - 1 field for the definition // .Named - 1 field for the definition
// .Struct - fields // .Struct - fields
// .Enum - fields // .Enum - fields
// .Tuple - parameters (procedures only) // .Parameters - parameters (procedures only)
entities: Array(Entity_Index), entities: Array(Entity_Index),
// Used By: .Struct, .Union // Used By: .Struct, .Union
+19 -5
View File
@@ -30,14 +30,27 @@ File_Tags :: struct {
} }
@require_results @require_results
get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { get_build_os_from_string :: proc(str: string) -> (found_os: runtime.Odin_OS_Type, found_subtarget: runtime.Odin_Platform_Subtarget_Type) {
str_os, _, str_subtarget := strings.partition(str, ":")
fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type)
for os in fields { for os in fields {
if strings.equal_fold(os.name, str) { if strings.equal_fold(os.name, str_os) {
return runtime.Odin_OS_Type(os.value) found_os = runtime.Odin_OS_Type(os.value)
break
} }
} }
return .Unknown if str_subtarget != "" {
st_fields := reflect.enum_fields_zipped(runtime.Odin_Platform_Subtarget_Type)
for subtarget in st_fields {
if strings.equal_fold(subtarget.name, str_subtarget) {
found_subtarget = runtime.Odin_Platform_Subtarget_Type(subtarget.value)
break
}
}
}
return
} }
@require_results @require_results
get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type {
@@ -187,7 +200,8 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags
if value == "ignore" { if value == "ignore" {
tags.ignore = true tags.ignore = true
} else if os := get_build_os_from_string(value); os != .Unknown { } else if os, subtarget := get_build_os_from_string(value); os != .Unknown {
_ = subtarget // TODO(bill): figure out how to handle the subtarget logic
if is_notted { if is_notted {
os_negative += {os} os_negative += {os}
} else { } else {
+17 -17
View File
@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
args = make([dynamic]^ast.Expr) args = make([dynamic]^ast.Expr)
for p.curr_tok.kind != .Close_Paren && for p.curr_tok.kind != .Close_Paren &&
p.curr_tok.kind != .EOF { p.curr_tok.kind != .EOF {
arg := parse_value(p) arg := parse_value(p)
if p.curr_tok.kind == .Eq { if p.curr_tok.kind == .Eq {
eq := expect_token(p, .Eq) eq := expect_token(p, .Eq)
if arg != nil { if arg != nil {
if _, ok := arg.derived.(^ast.Ident); !ok { if _, ok := arg.derived.(^ast.Ident); !ok {
error(p, arg.pos, "expected an identifier for 'key=value'") error(p, arg.pos, "expected an identifier for 'key=value'")
} }
} }
value := parse_value(p) value := parse_value(p)
fv := ast.new(ast.Field_Value, arg.pos, value) fv := ast.new(ast.Field_Value, arg.pos, value)
fv.field = arg fv.field = arg
fv.sep = eq.pos fv.sep = eq.pos
fv.value = value fv.value = value
arg = fv arg = fv
} }
append(&args, arg) append(&args, arg)
allow_token(p, .Comma) or_break allow_token(p, .Comma) or_break
} }
} }
p.expr_level -= 1 p.expr_level -= 1
+42 -41
View File
@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
return heap_allocator() return heap_allocator()
} }
temp_allocator_proc :: runtime.arena_allocator_proc
@(private="file") @(private="file")
MAX_TEMP_ARENA_COUNT :: 2 MAX_TEMP_ARENA_COUNT :: 2
@(private="file")
MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
@(private="file", thread_local) @(private="file", thread_local)
global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
@(private="file", thread_local)
global_default_temp_allocator_index: uint
@(require_results)
temp_allocator :: proc() -> runtime.Allocator {
arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
if arena.backing_allocator.procedure == nil {
arena.backing_allocator = heap_allocator()
}
return runtime.Allocator{
procedure = temp_allocator_proc,
data = arena,
}
}
@(require_results)
temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
return
}
temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
runtime.arena_temp_end(temp, loc)
}
@(fini, private) @(fini, private)
temp_allocator_fini :: proc() { temp_allocator_fini :: proc() {
for &arena in global_default_temp_allocator_arenas { for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
global_default_temp_allocator_arenas = {} global_default_temp_allocator_arenas = {}
} }
TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { Temp_Allocator :: struct {
runtime.arena_temp_end(temp, loc) using arena: ^runtime.Arena,
if temp.arena != nil { using allocator: runtime.Allocator,
global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT tmp: runtime.Arena_Temp,
} loc: runtime.Source_Code_Location,
}
TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
runtime.arena_temp_end(temp.tmp, temp.loc)
} }
@(deferred_out=TEMP_ALLOCATOR_GUARD_END) @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
tmp := temp_allocator_temp_begin(loc) good_arena: ^runtime.Arena
return tmp, loc for i in 0..<MAX_TEMP_ARENA_COUNT {
good_arena = &global_default_temp_allocator_arenas[i]
for c in collisions {
if good_arena == c.data {
good_arena = nil
}
}
if good_arena != nil {
break
}
}
assert(good_arena != nil)
if good_arena.backing_allocator.procedure == nil {
good_arena.backing_allocator = heap_allocator()
}
tmp := runtime.arena_temp_begin(good_arena, loc)
return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
}
temp_allocator_begin :: runtime.arena_temp_begin
temp_allocator_end :: runtime.arena_temp_end
@(deferred_out=_temp_allocator_end)
temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
return temp_allocator_begin(tmp.arena)
}
@(private="file")
_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
temp_allocator_end(tmp)
} }
@(init, private) @(init, private)
+56 -2
View File
@@ -2,6 +2,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:slice" import "core:slice"
import "core:strings"
read_dir :: read_directory read_dir :: read_directory
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
size = 100 size = 100
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
it := read_directory_iterator_create(f) it := read_directory_iterator_create(f)
defer _read_directory_iterator_destroy(&it) defer _read_directory_iterator_destroy(&it)
dfi := make([dynamic]File_Info, 0, size, temp_allocator()) dfi := make([dynamic]File_Info, 0, size, temp_allocator)
defer if err != nil { defer if err != nil {
for fi in dfi { for fi in dfi {
file_info_delete(fi, allocator) file_info_delete(fi, allocator)
@@ -192,3 +193,56 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
return _read_directory_iterator(it) return _read_directory_iterator(it)
} }
// Recursively copies a directory to `dst` from `src`
copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
when #defined(_copy_directory_all_native) {
return _copy_directory_all_native(dst, src, dst_perm)
} else {
return _copy_directory_all(dst, src, dst_perm)
}
}
@(private)
_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
err := make_directory(dst, dst_perm)
if err != nil && err != .Exist {
return err
}
temp_allocator := TEMP_ALLOCATOR_GUARD({})
abs_src := get_absolute_path(src, temp_allocator) or_return
abs_dst := get_absolute_path(dst, temp_allocator) or_return
dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
w: Walker
walker_init_path(&w, src)
defer walker_destroy(&w)
for info in walker_walk(&w) {
_ = walker_error(&w) or_break
rel := strings.trim_prefix(info.fullpath, abs_src)
non_zero_resize(&dst_buf, 0)
reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
append(&dst_buf, abs_dst)
append(&dst_buf, Path_Separator_String)
append(&dst_buf, rel)
if info.type == .Directory {
err = make_directory(string(dst_buf[:]), dst_perm)
if err != nil && err != .Exist {
return err
}
} else {
copy_file(string(dst_buf[:]), info.fullpath) or_return
}
}
_ = walker_error(&w) or_return
return nil
}
+2 -1
View File
@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
it.impl.prev_fi = fi it.impl.prev_fi = fi
if err != nil { if err != nil {
path, _ := _get_full_path(entry_fd, temp_allocator()) temp_allocator := TEMP_ALLOCATOR_GUARD({})
path, _ := _get_full_path(entry_fd, temp_allocator)
read_directory_iterator_set_error(it, path, err) read_directory_iterator_set_error(it, path, err)
} }
+17
View File
@@ -0,0 +1,17 @@
#+private
package os2
import "core:sys/darwin"
_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src, temp_allocator) or_return
cdst := clone_to_cstring(dst, temp_allocator) or_return
if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
err = _get_platform_error()
}
return
}
+5 -5
View File
@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return return
} }
path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
@(require_results) @(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
TEMP_ALLOCATOR_GUARD()
for !it.impl.no_more_files { for !it.impl.no_more_files {
err: Error err: Error
file_info_delete(it.impl.prev_fi, file_allocator()) file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
wpath = impl.wname[:i] wpath = impl.wname[:i]
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath_search := make([]u16, len(wpath)+3, temp_allocator()) wpath_search := make([]u16, len(wpath)+3, temp_allocator)
copy(wpath_search, wpath) copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+1] = '*'
+7 -7
View File
@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
cval := posix.getenv(ckey) cval := posix.getenv(ckey)
if cval == nil { if cval == nil {
return return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) or_return ckey := strings.clone_to_cstring(key, temp_allocator) or_return
cval := strings.clone_to_cstring(key, temp_allocator()) or_return cval := strings.clone_to_cstring(value, temp_allocator) or_return
if posix.setenv(ckey, cval, true) != nil { if posix.setenv(ckey, cval, true) != nil {
err = _get_platform_error_from_errno() err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
} }
_unset_env :: proc(key: string) -> (ok: bool) { _unset_env :: proc(key: string) -> (ok: bool) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
ok = posix.unsetenv(ckey) == .OK ok = posix.unsetenv(ckey) == .OK
return return
+2 -2
View File
@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
defer if err != nil { delete(g_env_buf, file_allocator()) } defer if err != nil { delete(g_env_buf, file_allocator()) }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs := make([]cstring, num_envs, temp_allocator()) or_return envs := make([]cstring, num_envs, temp_allocator) or_return
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
if _err != nil { if _err != nil {
+10 -10
View File
@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" { if key == "" {
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0) n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 { if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", true return "", true
} }
b := make([]u16, n+1, temp_allocator()) b := make([]u16, n+1, 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 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> Error { _set_env :: proc(key, value: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator()) or_return k := win32_utf8_to_wstring(key, temp_allocator) or_return
v := win32_utf8_to_wstring(value, temp_allocator()) or_return v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) { if !win32.SetEnvironmentVariableW(k, v) {
return _get_platform_error() return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
} }
_unset_env :: proc(key: string) -> bool { _unset_env :: proc(key: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k, _ := win32_utf8_to_wstring(key, temp_allocator()) k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, nil)) return bool(win32.SetEnvironmentVariableW(k, nil))
} }
_clear_env :: proc() { _clear_env :: proc() {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs, _ := environ(temp_allocator()) envs, _ := environ(temp_allocator)
for env in envs { for env in envs {
for j in 1..<len(env) { for j in 1..<len(env) {
if env[j] == '=' { if env[j] == '=' {
+2 -2
View File
@@ -108,12 +108,12 @@ error_string :: proc(ferr: Error) -> string {
} }
print_error :: proc(f: ^File, ferr: Error, msg: string) { print_error :: proc(f: ^File, ferr: Error, msg: string) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr) err_str := error_string(ferr)
// msg + ": " + err_str + '\n' // msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1 length := len(msg) + 2 + len(err_str) + 1
buf := make([]u8, length, temp_allocator()) buf := make([]u8, length, temp_allocator)
copy(buf, msg) copy(buf, msg)
buf[len(msg)] = ':' buf[len(msg)] = ':'
+13 -4
View File
@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
@(require_results) @(require_results)
is_file :: proc(path: string) -> bool { is_file :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -303,8 +303,8 @@ is_dir :: is_directory
@(require_results) @(require_results)
is_directory :: proc(path: string) -> bool { is_directory :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
copy_file :: proc(dst_path, src_path: string) -> Error { copy_file :: proc(dst_path, src_path: string) -> Error {
when #defined(_copy_file_native) {
return _copy_file_native(dst_path, src_path)
} else {
return _copy_file(dst_path, src_path)
}
}
@(private)
_copy_file :: proc(dst_path, src_path: string) -> Error {
src := open(src_path) or_return src := open(src_path) or_return
defer close(src) defer close(src)
+29 -29
View File
@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
} }
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
// Just default to using O_NOCTTY because needing to open a controlling // Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while // terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
linux.close(fd) linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_name, new_name: string) -> Error { _rename :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
} }
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_link_cstr(name_cstr, allocator) return _read_link_cstr(name_cstr, allocator)
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chdir(name_cstr)) return _get_platform_error(linux.chdir(name_cstr))
} }
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
} }
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
times := [2]linux.Time_Spec { times := [2]linux.Time_Spec {
{ {
uint(atime._nsec) / uint(time.Second), uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(name: string) -> bool { _exists :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr, _ := temp_cstring(name) name_cstr, _ := clone_to_cstring(name, temp_allocator)
return linux.access(name_cstr, linux.F_OK) == .NONE return linux.access(name_cstr, linux.F_OK) == .NONE
} }
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := clone_to_cstring(name, temp_allocator()) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_entire_pseudo_file_cstring(name_cstr, allocator) return _read_entire_pseudo_file_cstring(name_cstr, allocator)
} }
+36 -35
View File
@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
if .Trunc in flags { sys_flags += {.TRUNC} } if .Trunc in flags { sys_flags += {.TRUNC} }
if .Inheritable in flags { sys_flags -= {.CLOEXEC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
if fd < 0 { if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
return nil return nil
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.remove(cname) != 0 { if posix.remove(cname) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_path) cold := clone_to_cstring(old_path, temp_allocator) or_return
cnew := temp_cstring(new_path) cnew := clone_to_cstring(new_path, temp_allocator) or_return
if posix.rename(cold, cnew) != 0 { if posix.rename(cold, cnew) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.link(cold, cnew) != .OK { if posix.link(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.symlink(cold, cnew) != .OK { if posix.symlink(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = allocator buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
} }
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chdir(cname) != .OK { if posix.chdir(cname) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
return nil return nil
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
return nil return nil
} }
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
} }
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
times := [2]posix.timeval{ times := [2]posix.timeval{
{ {
tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
}, },
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.utimes(cname, &times) != .OK { if posix.utimes(cname, &times) != .OK {
return _get_platform_error() return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath, err := clone_to_cstring(path, temp_allocator)
if err != nil { return false }
return posix.access(cpath) == .OK return posix.access(cpath) == .OK
} }
+28
View File
@@ -3,6 +3,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:sys/darwin"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
return clone_to_cstring(string(cstring(&buf[0])), allocator) return clone_to_cstring(string(cstring(&buf[0])), allocator)
} }
_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src_path, temp_allocator) or_return
cdst := clone_to_cstring(dst_path, temp_allocator) or_return
// Disallow directories, as specified by the generic implementation.
stat: posix.stat_t
if posix.stat(csrc, &stat) != .OK {
err = _get_platform_error()
return
}
if posix.S_ISDIR(stat.st_mode) {
err = .Invalid_File
return
}
ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
if ret < 0 {
err = _get_platform_error()
}
return
}
+2 -2
View File
@@ -7,8 +7,8 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator)
buf: [posix.PATH_MAX]byte buf: [posix.PATH_MAX]byte
path = posix.realpath(cname, raw_data(buf[:])) path = posix.realpath(cname, raw_data(buf[:]))
+31 -33
View File
@@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
err = .Not_Exist err = .Not_Exist
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path := _fix_long_path(name, temp_allocator()) or_return path := _fix_long_path(name, temp_allocator) or_return
access: u32 access: u32
switch flags & {.Read, .Write} { switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ case {.Read}: access = win32.FILE_GENERIC_READ
@@ -506,10 +506,16 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER length: win32.LARGE_INTEGER
if f.kind == .Pipe {
return 0, .No_Size
}
handle := _handle(&f.file) handle := _handle(&f.file)
if f.kind == .Pipe {
bytes_available: u32
if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
return i64(bytes_available), nil
} else {
err = _get_platform_error()
return
}
}
if !win32.GetFileSizeEx(handle, &length) { if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error() err = _get_platform_error()
} }
@@ -551,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
err, err1: Error err, err1: Error
if !win32.DeleteFileW(p) { if !win32.DeleteFileW(p) {
err = _get_platform_error() err = _get_platform_error()
@@ -589,9 +595,9 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
from := _fix_long_path(old_path, temp_allocator()) or_return from := _fix_long_path(old_path, temp_allocator) or_return
to := _fix_long_path(new_path, temp_allocator()) or_return to := _fix_long_path(new_path, temp_allocator) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil return nil
} }
@@ -600,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
o := _fix_long_path(old_name, temp_allocator()) or_return o := _fix_long_path(old_name, temp_allocator) or_return
n := _fix_long_path(new_name, temp_allocator()) or_return n := _fix_long_path(new_name, temp_allocator) or_return
if win32.CreateHardLinkW(n, o, nil) { if win32.CreateHardLinkW(n, o, nil) {
return nil return nil
} }
@@ -663,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -689,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local @thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
handle := _open_sym_link(p) or_return handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -766,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
if !win32.SetCurrentDirectoryW(p) { if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error() return _get_platform_error()
} }
@@ -794,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
defer close(f) defer close(f)
return _fchtimes(f, atime, mtime) return _fchtimes(f, atime, mtime)
} }
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
if f == nil || f.impl == nil { if f == nil || f.impl == nil {
return nil return nil
} }
d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(_handle(f), &d) {
return _get_platform_error()
}
to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
}
atime, mtime := atime, mtime atime, mtime := atime, mtime
if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
@@ -814,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
info: win32.FILE_BASIC_INFO info: win32.FILE_BASIC_INFO
info.LastAccessTime = to_windows_time(atime) info.LastAccessTime = time_as_filetime(atime)
info.LastWriteTime = to_windows_time(mtime) info.LastWriteTime = time_as_filetime(mtime)
if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath, _ := _fix_long_path(path, temp_allocator()) wpath, _ := _fix_long_path(path, temp_allocator)
attribs := win32.GetFileAttributesW(wpath) attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES return attribs != win32.INVALID_FILE_ATTRIBUTES
} }
-5
View File
@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
return cstring(&buf[0]), nil return cstring(&buf[0]), nil
} }
@(require_results)
temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
return clone_to_cstring(s, temp_allocator())
}
@(require_results) @(require_results)
string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
s := string(b) s := string(b)
+4 -4
View File
@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
return strings.clone(".", allocator) return strings.clone(".", allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// The extra byte is to simplify appending path elements by letting the // The extra byte is to simplify appending path elements by letting the
// loop to end each with a separator. We'll trim the last one when we're done. // loop to end each with a separator. We'll trim the last one when we're done.
buffer := make([]u8, len(path) + 1, temp_allocator()) or_return buffer := make([]u8, len(path) + 1, temp_allocator) or_return
// This is the only point where Windows and POSIX differ, as Windows has // This is the only point where Windows and POSIX differ, as Windows has
// alphabet-based volumes for root paths. // alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
for e, i in elems { for e, i in elems {
if e != "" { if e != "" {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
return clean_path(p, allocator) return clean_path(p, allocator)
} }
} }
+1 -1
View File
@@ -25,5 +25,5 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return return
} }
return string(buf[:size]), nil return string(buf[:size-1]), nil
} }
+29 -9
View File
@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(path: string, perm: int) -> Error { _mkdir :: proc(path: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
} }
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
return _get_platform_error(errno) return _get_platform_error(errno)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// need something we can edit, and use to generate cstrings // need something we can edit, and use to generate cstrings
path_bytes := make([]u8, len(path) + 1, temp_allocator()) path_bytes := make([]u8, len(path) + 1, temp_allocator)
// zero terminate the byte slice to make it a valid cstring // zero terminate the byte slice to make it a valid cstring
copy(path_bytes, path) copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
} }
_set_working_directory :: proc(dir: string) -> Error { _set_working_directory :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
return _get_platform_error(linux.chdir(dir_cstr)) return _get_platform_error(linux.chdir(dir_cstr))
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n, errno := linux.readlink("/proc/self/exe", buf[:]) n, errno := linux.readlink("/proc/self/exe", buf[:])
if errno != .NONE { if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
if errno != nil {
err = _get_platform_error(errno)
return
}
defer linux.close(fd)
return _get_full_path(fd, allocator)
}
+2 -2
View File
@@ -5,9 +5,9 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
if n < 0 { if n < 0 {
+3 -3
View File
@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return real(arg, allocator) return real(arg, allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := strings.builder_make(temp_allocator()) buf := strings.builder_make(temp_allocator)
paths := get_env("PATH", temp_allocator()) paths := get_env("PATH", temp_allocator)
for dir in strings.split_iterator(&paths, ":") { for dir in strings.split_iterator(&paths, ":") {
strings.builder_reset(&buf) strings.builder_reset(&buf)
strings.write_string(&buf, dir) strings.write_string(&buf, dir)
+30 -13
View File
@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == _Path_Separator return c == _Path_Separator
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) or_return clean_path := clean_path(path, temp_allocator) or_return
return internal_mkdir_all(clean_path, perm) return internal_mkdir_all(clean_path, perm)
internal_mkdir_all :: proc(path: string, perm: int) -> Error { internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
} }
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath := clone_to_cstring(path, temp_allocator) or_return
dir := posix.opendir(cpath) dir := posix.opendir(cpath)
if dir == nil { if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
continue continue
} }
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
if entry.d_type == .DIR { if entry.d_type == .DIR {
_remove_all(fullpath[:len(fullpath)-1]) or_return _remove_all(fullpath[:len(fullpath)-1]) or_return
} else { } else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
} }
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = temp_allocator() buf.allocator = temp_allocator
size := uint(posix.PATH_MAX) size := uint(posix.PATH_MAX)
cwd: cstring cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cdir := temp_cstring(dir) cdir := clone_to_cstring(dir, temp_allocator) or_return
if posix.chdir(cdir) != .OK { if posix.chdir(cdir) != .OK {
err = _get_platform_error() err = _get_platform_error()
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := clone_string(string(path_ptr), allocator) or_return
return path_str, nil
}
-21
View File
@@ -4,10 +4,6 @@ package os2
// This implementation is for all systems that have POSIX-compliant filesystem paths. // This implementation is for all systems that have POSIX-compliant filesystem paths.
import "base:runtime"
import "core:strings"
import "core:sys/posix"
_are_paths_identical :: proc(a, b: string) -> (identical: bool) { _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
return a == b return a == b
} }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
return len(path) > 0 && _is_path_separator(path[0]) return len(path) > 0 && _is_path_separator(path[0])
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
TEMP_ALLOCATOR_GUARD()
rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := strings.clone(string(path_ptr), allocator)
return path_str, nil
}
_get_relative_path_handle_start :: proc(base, target: string) -> bool { _get_relative_path_handle_start :: proc(base, target: string) -> bool {
base_rooted := len(base) > 0 && _is_path_separator(base[0]) base_rooted := len(base) > 0 && _is_path_separator(base[0])
target_rooted := len(target) > 0 && _is_path_separator(target[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0])
+2 -2
View File
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) clean_path := clean_path(path, temp_allocator)
return internal_mkdir_all(clean_path) return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error { internal_mkdir_all :: proc(path: string) -> Error {
+23 -19
View File
@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return p, false, nil return p, false, nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_stat, err := stat(path, temp_allocator()) dir_stat, err := stat(path, temp_allocator)
if err == nil { if err == nil {
if dir_stat.type == .Directory { if dir_stat.type == .Directory {
return nil return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
err = mkdir(path, perm) err = mkdir(path, perm)
if err != nil { if err != nil {
new_dir_stat, err1 := lstat(path, temp_allocator()) new_dir_stat, err1 := lstat(path, temp_allocator)
if err1 == nil && new_dir_stat.type == .Directory { if err1 == nil && new_dir_stat.type == .Directory {
return nil return nil
} }
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir := win32_utf8_to_wstring(path, temp_allocator()) or_return dir := win32_utf8_to_wstring(path, temp_allocator) or_return
empty: [1]u16 empty: [1]u16
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
sz_utf16 := win32.GetCurrentDirectoryW(0, nil) sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]u16, 512, temp_allocator()) or_return buf := make([dynamic]u16, 512, temp_allocator) or_return
for { for {
ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
if ret == 0 { if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
if value == 1 { if value == 1 {
can_use_long_paths = true can_use_long_paths = true
} }
} }
@(require_results) @(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
return path return path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
PREFIX :: `\\?` PREFIX :: `\\?`
path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
copy(path_buf, PREFIX) copy(path_buf, PREFIX)
n := len(path) n := len(path)
r, w := 0, len(PREFIX) r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
start += 1 start += 1
} }
copy(buffer, path[:start]) copy(buffer, path[:start])
for n in 0..<start {
if _is_path_separator(buffer[n]) {
buffer[n] = _Path_Separator
}
}
} }
return return
} }
@@ -297,14 +301,14 @@ _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absol
if rel == "" { if rel == "" {
rel = "." rel = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator()) rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
} }
buf := make([]u16, n, temp_allocator()) or_return buf := make([]u16, n, temp_allocator) or_return
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
+4 -14
View File
@@ -264,7 +264,7 @@ specific process, even after it has died.
**Note(linux)**: The `handle` will be referring to pidfd. **Note(linux)**: The `handle` will be referring to pidfd.
*/ */
Process :: struct { Process :: struct {
pid: int, pid: int,
handle: uintptr, handle: uintptr,
} }
@@ -290,21 +290,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
return _process_open(pid, flags) return _process_open(pid, flags)
} }
/*
OS-specific process attributes.
*/
Process_Attributes :: struct {
sys_attr: _Sys_Process_Attributes,
}
/* /*
The description of how a process should be created. The description of how a process should be created.
*/ */
Process_Desc :: struct { Process_Desc :: struct {
// OS-specific attributes.
sys_attr: Process_Attributes,
// The working directory of the process. If the string has length 0, the // The working directory of the process. If the string has length 0, the
// working directory is assumed to be the current working directory of the // working directory is assumed to be the current working directory of the
// current process. // current process.
@@ -407,11 +396,9 @@ process_exec :: proc(
{ {
stdout_b: [dynamic]byte stdout_b: [dynamic]byte
stdout_b.allocator = allocator stdout_b.allocator = allocator
defer stdout = stdout_b[:]
stderr_b: [dynamic]byte stderr_b: [dynamic]byte
stderr_b.allocator = allocator stderr_b.allocator = allocator
defer stderr = stderr_b[:]
buf: [1024]u8 = --- buf: [1024]u8 = ---
@@ -450,6 +437,9 @@ process_exec :: proc(
} }
} }
} }
stdout = stdout_b[:]
stderr = stderr_b[:]
} }
if err != nil { if err != nil {
+50 -41
View File
@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
@(private="package") @(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
} }
defer linux.close(dir_fd) defer linux.close(dir_fd)
dynamic_list := make([dynamic]int, temp_allocator()) or_return dynamic_list := make([dynamic]int, temp_allocator) or_return
buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
loop: for { loop: for {
buflen: int buflen: int
buflen, errno = linux.getdents(dir_fd, buf[:]) buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
@(private="package") @(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
passwd_bytes: []u8 passwd_bytes: []u8
passwd_err: Error passwd_err: Error
passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
if passwd_err != nil { if passwd_err != nil {
err = passwd_err err = passwd_err
break username_if break username_if
@@ -162,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} { cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cmdline") strings.write_string(&path_builder, "/cmdline")
cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if cmdline_err != nil || len(cmdline_bytes) == 0 { if cmdline_err != nil || len(cmdline_bytes) == 0 {
err = cmdline_err err = cmdline_err
break cmdline_if break cmdline_if
@@ -178,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
terminator := strings.index_byte(cmdline, 0) terminator := strings.index_byte(cmdline, 0)
assert(terminator > 0) assert(terminator > 0)
command_line_exec := cmdline[:terminator] // command_line_exec := cmdline[:terminator]
// Still need cwd if the execution on the command line is relative. // Still need cwd if the execution on the command line is relative.
cwd: string cwd: string
cwd_err: Error cwd_err: Error
if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') { if .Working_Dir in selection {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cwd") strings.write_string(&path_builder, "/cwd")
cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
if cwd_err == nil && .Working_Dir in selection { if cwd_err == nil && .Working_Dir in selection {
info.working_dir = strings.clone(cwd, allocator) or_return info.working_dir = strings.clone(cwd, allocator) or_return
info.fields += {.Working_Dir} info.fields += {.Working_Dir}
@@ -199,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Executable_Path in selection {
if cmdline[0] == '/' {
info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
info.fields += {.Executable_Path}
} else if cwd_err == nil {
info.executable_path = join_path({ cwd, cmdline[:terminator] }, allocator) or_return
info.fields += {.Executable_Path}
} else {
break cmdline_if
}
}
if selection & {.Command_Line, .Command_Args} != {} { if selection & {.Command_Line, .Command_Args} != {} {
// skip to first arg // skip to first arg
//cmdline = cmdline[terminator + 1:] //cmdline = cmdline[terminator + 1:]
@@ -257,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if stat_err != nil { if stat_err != nil {
err = stat_err err = stat_err
break stat_if break stat_if
@@ -296,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
Nice, Nice,
//... etc, //... etc,
} }
stat_fields := strings.split(stats, " ", temp_allocator()) or_return stat_fields := strings.split(stats, " ", temp_allocator) or_return
if len(stat_fields) <= int(Fields.Nice) { if len(stat_fields) <= int(Fields.Nice) {
break stat_if break stat_if
@@ -323,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Executable_Path in selection {
/*
NOTE(Jeroen):
The old version returned the wrong executable path for things like `bash` or `sh`,
for whom `/proc/<pid>/cmdline` will just report "bash" or "sh",
resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
Thanks to Yawning for suggesting `/proc/self/exe`.
*/
strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/exe")
if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
info.fields += {.Executable_Path}
} else {
err = exe_err
}
}
if .Environment in selection { if .Environment in selection {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/environ") strings.write_string(&path_builder, "/environ")
if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil { if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
env := string(env_bytes) env := string(env_bytes)
env_list := make([dynamic]string, allocator) or_return env_list := make([dynamic]string, allocator) or_return
@@ -378,12 +390,9 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
return return
} }
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(desc.command) == 0 { if len(desc.command) == 0 {
return process, .Invalid_Command return process, .Invalid_Command
@@ -392,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
dir_fd := linux.AT_FDCWD dir_fd := linux.AT_FDCWD
errno: linux.Errno errno: linux.Errno
if desc.working_dir != "" { if desc.working_dir != "" {
dir_cstr := temp_cstring(desc.working_dir) or_return dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
return process, _get_platform_error(errno) return process, _get_platform_error(errno)
} }
@@ -405,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
exe_path: cstring exe_path: cstring
executable_name := desc.command[0] executable_name := desc.command[0]
if strings.index_byte(executable_name, '/') < 0 { if strings.index_byte(executable_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
exe_builder := strings.builder_make(temp_allocator()) or_return exe_builder := strings.builder_make(temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -435,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
} }
} else { } else {
exe_path = temp_cstring(executable_name) or_return exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
if linux.access(exe_path, linux.X_OK) != .NONE { if linux.access(exe_path, linux.X_OK) != .NONE {
return process, .Not_Exist return process, .Not_Exist
} }
@@ -443,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// args and environment need to be a list of cstrings // args and environment need to be a list of cstrings
// that are terminated by a nil pointer. // that are terminated by a nil pointer.
cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
for command, i in desc.command { for command, i in desc.command {
cargs[i] = temp_cstring(command) or_return cargs[i] = clone_to_cstring(command, temp_allocator) or_return
} }
// Use current process' environment if description didn't provide it. // Use current process' environment if description didn't provide it.
env: [^]cstring env: [^]cstring
if desc.env == nil { if desc.env == nil {
// take this process's current environment // take this process's current environment
env = raw_data(export_cstring_environment(temp_allocator())) env = raw_data(export_cstring_environment(temp_allocator))
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) or_return cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = &cenv[0] env = &cenv[0]
} }
@@ -584,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
stat_path_buf: [48]u8 stat_path_buf: [48]u8
path_builder := strings.builder_from_bytes(stat_path_buf[:]) path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -593,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
stat_buf: []u8 stat_buf: []u8
stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if err != nil { if err != nil {
return return
} }
+9 -11
View File
@@ -46,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return _process_info_by_pid(_get_pid(), selection, allocator) return _process_info_by_pid(_get_pid(), selection, allocator)
} }
_Sys_Process_Attributes :: struct {}
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if len(desc.command) == 0 { if len(desc.command) == 0 {
err = .Invalid_Path err = .Invalid_Path
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// search PATH if just a plain name is provided. // search PATH if just a plain name is provided.
exe_builder := strings.builder_make(temp_allocator()) exe_builder := strings.builder_make(temp_allocator)
exe_name := desc.command[0] exe_name := desc.command[0]
if strings.index_byte(exe_name, '/') < 0 { if strings.index_byte(exe_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -110,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
cwd: cstring; if desc.working_dir != "" { cwd: cstring; if desc.working_dir != "" {
cwd = temp_cstring(desc.working_dir) cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
} }
cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
for part, i in desc.command { for part, i in desc.command {
cmd[i] = temp_cstring(part) cmd[i] = clone_to_cstring(part, temp_allocator) or_return
} }
env: [^]cstring env: [^]cstring
@@ -123,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// take this process's current environment // take this process's current environment
env = posix.environ env = posix.environ
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = raw_data(cenv) env = raw_data(cenv)
} }
+4 -3
View File
@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
break args break args
} }
buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
if err == nil { if err == nil {
err = _get_platform_error() err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buffer := make([]i32, ret, temp_allocator()) buffer := make([]i32, ret, temp_allocator)
ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
if ret < 0 { if ret < 0 {
err = _get_platform_error() err = _get_platform_error()
-2
View File
@@ -44,8 +44,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return return
} }
_Sys_Process_Attributes :: struct {}
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = .Unsupported err = .Unsupported
return return
+63 -32
View File
@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -417,35 +419,64 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
return return
} }
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
command_line := _build_command_line(desc.command, temp_allocator()) command_line := _build_command_line(desc.command, temp_allocator)
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
environment := desc.env environment := desc.env
if desc.env == nil { if desc.env == nil {
environment = environ(temp_allocator()) or_return environment = environ(temp_allocator) or_return
} }
environment_block := _build_environment_block(environment, temp_allocator()) environment_block := _build_environment_block(environment, temp_allocator)
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
if desc.stdout != nil { stderr_handle: win32.HANDLE
stdout_handle: win32.HANDLE
stdin_handle: win32.HANDLE
null_handle: win32.HANDLE
if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
null_handle = win32.CreateFileW(
win32.L("NUL"),
win32.GENERIC_READ|win32.GENERIC_WRITE,
win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
&win32.SECURITY_ATTRIBUTES{
nLength = size_of(win32.SECURITY_ATTRIBUTES),
bInheritHandle = true,
},
win32.OPEN_EXISTING,
win32.FILE_ATTRIBUTE_NORMAL,
nil,
)
// Opening NUL should always succeed.
assert(null_handle != nil)
}
// NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
// and we don't have to hold onto this until the process exits.
defer if null_handle != nil {
win32.CloseHandle(null_handle)
}
if desc.stdout == nil {
stdout_handle = null_handle
} else {
stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
} }
if desc.stderr != nil {
if desc.stderr == nil {
stderr_handle = null_handle
} else {
stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
} }
if desc.stdin != nil {
if desc.stdin == nil {
stdin_handle = null_handle
} else {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
} }
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
process_info: win32.PROCESS_INFORMATION process_info: win32.PROCESS_INFORMATION
ok := win32.CreateProcessW( ok := win32.CreateProcessW(
nil, nil,
@@ -583,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
} }
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
token_handle: win32.HANDLE token_handle: win32.HANDLE
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
err = _get_platform_error() err = _get_platform_error()
@@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
} }
err = nil err = nil
} }
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
err = _get_platform_error() err = _get_platform_error()
return return
@@ -614,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
err = _get_platform_error() err = _get_platform_error()
return return
} }
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
return strings.concatenate({domain, "\\", username}, allocator) return strings.concatenate({domain, "\\", username}, allocator)
} }
+4 -4
View File
@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
@(require_results) @(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) { modification_time :: proc(f: ^File) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := fstat(f, temp_allocator()) fi, err := fstat(f, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
@(require_results) @(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) { modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
+4 -4
View File
@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
// 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: runtime.Allocator) -> (fi: File_Info, err: Error) { _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {}) fd, errno := linux.open(name_cstr, {})
if errno != .NONE { if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
} }
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if errno != .NONE { if errno != .NONE {
+9 -8
View File
@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) or_return cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, {}) fd := posix.open(cname, {})
if fd == -1 { if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
// NOTE: This might not be correct when given "/symlink/foo.txt", // NOTE: This might not be correct when given "/symlink/foo.txt",
// you would want that to resolve "/symlink", but not resolve "foo.txt". // you would want that to resolve "/symlink", but not resolve "foo.txt".
fullpath := clean_path(name, temp_allocator()) or_return fullpath := clean_path(name, temp_allocator) or_return
assert(len(fullpath) > 0) assert(len(fullpath) > 0)
switch { switch {
case fullpath[0] == '/': case fullpath[0] == '/':
// nothing. // nothing.
case fullpath == ".": case fullpath == ".":
fullpath = getwd(temp_allocator()) or_return fullpath = getwd(temp_allocator) or_return
case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
fullpath = fullpath[2:] fullpath = fullpath[2:]
fallthrough fallthrough
case: case:
fullpath = concatenate({ fullpath = concatenate({
getwd(temp_allocator()) or_return, getwd(temp_allocator) or_return,
"/", "/",
fullpath, fullpath,
}, temp_allocator()) or_return }, temp_allocator) or_return
} }
stat: posix.stat_t stat: posix.stat_t
if posix.lstat(temp_cstring(fullpath), &stat) != .OK { c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
if posix.lstat(c_fullpath, &stat) != .OK {
err = _get_platform_error() err = _get_platform_error()
return return
} }
+87 -45
View File
@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
name = "." name = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := win32_utf8_to_utf16(name, temp_allocator()) or_return p := win32_utf8_to_utf16(name, temp_allocator) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
} }
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
if len(name) == 0 { if len(name) == 0 {
return {}, .Not_Exist return {}, .Not_Exist
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wname := _fix_long_path(name, temp_allocator()) or_return wname := _fix_long_path(name, temp_allocator) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_from_buf(buf[:n], allocator) return _cleanpath_from_buf(buf[:n], allocator)
} }
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
return nil, _get_platform_error() return nil, _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil return _cleanpath_strip_prefix(buf[:n]), nil
} }
@@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
return return
} }
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
win := u64(t._nsec / 100) + 116444736000000000
return win32.LARGE_INTEGER(win)
}
filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
return {_nsec=(i64(ft) - 116444736000000000) * 100}
}
filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
}
filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
@@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
@@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
return fi, nil return fi, nil
} }
@@ -310,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
return false return false
} }
_is_UNC :: proc(path: string) -> bool { _volume_name_len :: proc(path: string) -> (length: int) {
return _volume_name_len(path) > 2
}
_volume_name_len :: proc(path: string) -> int {
if len(path) < 2 { if len(path) < 2 {
return 0 return 0
} }
c := path[0]
if path[1] == ':' { if path[1] == ':' {
switch c { switch path[0] {
case 'a'..='z', 'A'..='Z': case 'a'..='z', 'A'..='Z':
return 2 return 2
} }
} }
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx /*
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
!_is_path_separator(path[2]) && path[2] != '.' { Further allowed paths can be of the form of:
for n := 3; n < l-1; n += 1 { - \\server\share or \\server\share\more\path
if _is_path_separator(path[n]) { - \\?\C:\...
n += 1 - \\.\PhysicalDriveX
if !_is_path_separator(path[n]) { */
if path[n] == '.' { // Any remaining kind of path has to start with two slashes.
break if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
} return 0
} }
for ; n < l; n += 1 {
if _is_path_separator(path[n]) { // Device path. The volume name is the whole string
break if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
} return len(path)
} }
return n
// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
prefix := 2
// File namespace.
if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
if _is_path_separator(path[4]) {
// `\\?\\` UNC path in file namespace
prefix = 5
}
if len(path) >= 6 && path[5] == ':' {
switch path[4] {
case 'a'..='z', 'A'..='Z':
return 6
case:
return 0
} }
break
} }
} }
return 0
} // UNC path, minimum version of the volume is `\\h\s` for host, share.
// Can also contain an IP address in the host position.
slash_count := 0
for i in prefix..<len(path) {
// Host needs to be at least 1 character
if _is_path_separator(path[i]) && i > 0 {
slash_count += 1
if slash_count == 2 {
return i
}
}
}
return len(path)
}

Some files were not shown because too many files have changed in this diff Show More