diff --git a/base/runtime/default_allocators_arena.odin b/base/runtime/default_allocators_arena.odin index fcc98abfa..571590f93 100644 --- a/base/runtime/default_allocators_arena.odin +++ b/base/runtime/default_allocators_arena.odin @@ -12,6 +12,7 @@ Memory_Block :: struct { capacity: uint, } +// NOTE: This is for internal use, prefer `Arena` from `core:mem/virtual` if necessary Arena :: struct { backing_allocator: Allocator, curr_block: ^Memory_Block, diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin index e49698e6e..7d7252625 100644 --- a/base/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+no-instrumentation package runtime diff --git a/base/runtime/heap_allocator_unix.odin b/base/runtime/heap_allocator_unix.odin index 2b6698885..a8a4e9169 100644 --- a/base/runtime/heap_allocator_unix.odin +++ b/base/runtime/heap_allocator_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+private package runtime diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin index 9cd065ff6..46ce51166 100644 --- a/base/runtime/os_specific_bsd.odin +++ b/base/runtime/os_specific_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd //+private package runtime @@ -9,7 +9,11 @@ foreign libc { @(link_name="write") _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- - __error :: proc() -> ^i32 --- + when ODIN_OS == .NetBSD { + @(link_name="__errno") __error :: proc() -> ^i32 --- + } else { + __error :: proc() -> ^i32 --- + } } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { diff --git a/build_odin.sh b/build_odin.sh index 76c4e1198..7e0d0e585 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -71,6 +71,10 @@ FreeBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; +NetBSD) + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 7af763706..d28a24f56 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -40,7 +40,7 @@ when ODIN_OS == .FreeBSD { ERANGE :: 34 } -when ODIN_OS == .OpenBSD { +when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { @(private="file") @(default_calling_convention="c") foreign libc { diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index b83ddecc8..f17d3bd06 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -83,7 +83,7 @@ when ODIN_OS == .Linux { } } -when ODIN_OS == .OpenBSD { +when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { fpos_t :: distinct i64 _IOFBF :: 0 diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 4c4280f30..924cf4aec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,7 +45,7 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index cbce220d4..a41fe7fac 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -22,7 +22,7 @@ when ODIN_OS == .Windows { wctrans_t :: distinct int wctype_t :: distinct u32 -} else when ODIN_OS == .OpenBSD { +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { wctrans_t :: distinct rawptr wctype_t :: distinct rawptr diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin index a31e4f2b2..641b72933 100644 --- a/core/crypto/rand_bsd.odin +++ b/core/crypto/rand_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd package crypto foreign import libc "system:c" diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index 4ea61ec91..46fb881b3 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -2,6 +2,7 @@ //+build !windows //+build !openbsd //+build !freebsd +//+build !netbsd //+build !darwin //+build !js package crypto diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 97f70b576..fb0270ea3 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd //+private package dynlib diff --git a/core/mem/virtual/virtual_bsd.odin b/core/mem/virtual/virtual_bsd.odin index 7568084c0..12e7818ef 100644 --- a/core/mem/virtual/virtual_bsd.odin +++ b/core/mem/virtual/virtual_bsd.odin @@ -1,9 +1,7 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd //+private package mem_virtual - - _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { return nil, nil } @@ -11,16 +9,18 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { return nil } + _decommit :: proc "contextless" (data: rawptr, size: uint) { } + _release :: proc "contextless" (data: rawptr, size: uint) { } + _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { return false } _platform_memory_init :: proc() { - } _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { diff --git a/core/net/dns.odin b/core/net/dns.odin index 48cd8bf29..408fd201b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -30,7 +30,7 @@ when ODIN_OS == .Windows { resolv_conf = "", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", } -} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "/etc/resolv.conf", hosts_file = "/etc/hosts", @@ -854,4 +854,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator } return _records[:], true -} \ No newline at end of file +} diff --git a/core/os/dir_freebsd.odin b/core/os/dir_bsd.odin similarity index 98% rename from core/os/dir_freebsd.odin rename to core/os/dir_bsd.odin index 2965182cd..c0dc8ad1f 100644 --- a/core/os/dir_freebsd.odin +++ b/core/os/dir_bsd.odin @@ -1,3 +1,4 @@ +//+build freebsd, netbsd package os import "core:mem" diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin new file mode 100644 index 000000000..40672face --- /dev/null +++ b/core/os/os2/allocators.odin @@ -0,0 +1,48 @@ +//+private +package os2 + +import "base:runtime" + +@(require_results) +file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() +} + +temp_allocator_proc :: runtime.arena_allocator_proc + +@(private="file", thread_local) +global_default_temp_allocator_arena: runtime.Arena + +@(require_results) +temp_allocator :: proc() -> runtime.Allocator { + return runtime.Allocator{ + procedure = temp_allocator_proc, + data = &global_default_temp_allocator_arena, + } +} + +@(require_results) +temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { + temp = runtime.arena_temp_begin(&global_default_temp_allocator_arena, loc) + return +} + +temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { + runtime.arena_temp_end(temp, loc) +} + +@(fini, private) +temp_allocator_fini :: proc() { + runtime.arena_destroy(&global_default_temp_allocator_arena) + global_default_temp_allocator_arena = {} +} + +@(deferred_out=temp_allocator_temp_end) +TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { + if ignore { + return {}, loc + } else { + return temp_allocator_temp_begin(loc), loc + } +} + diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index bed4bebd9..c8d39b270 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -6,6 +6,7 @@ import "base:runtime" // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator +@(require_results) get_env :: proc(key: string, allocator: runtime.Allocator) -> string { value, _ := lookup_env(key, allocator) return value @@ -15,6 +16,7 @@ get_env :: proc(key: string, allocator: runtime.Allocator) -> string { // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator +@(require_results) lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { return _lookup_env(key, allocator) } @@ -38,6 +40,7 @@ clear_env :: proc() { // environ returns a copy of strings representing the environment, in the form "key=value" // NOTE: the slice of strings and the strings with be allocated using the supplied allocator +@(require_results) environ :: proc(allocator: runtime.Allocator) -> []string { return _environ(allocator) } diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 774af9e8f..39694b821 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -19,9 +19,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - b := make([]u16, n+1, _temp_allocator()) + b := make([]u16, n+1, temp_allocator()) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { @@ -50,8 +50,8 @@ _unset_env :: proc(key: string) -> bool { } _clear_env :: proc() { - _TEMP_ALLOCATOR_GUARD() - envs := environ(_temp_allocator()) + TEMP_ALLOCATOR_GUARD() + envs := environ(temp_allocator()) for env in envs { for j in 1.. (cname: cstring) { - return strings.clone_to_cstring(name, context.temp_allocator) -} - - @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { f := (^File)(stream_data) ferr: Error - i: int switch mode { case .Read: n, ferr = _read(f, p) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 0708f708e..977979bae 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -76,6 +76,7 @@ read_entire_file :: proc{ read_entire_file_from_file, } +@(require_results) read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) { f, ferr := open(name) if ferr != nil { @@ -85,14 +86,15 @@ read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) - return read_entire_file_from_file(f, allocator) } +@(require_results) read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (data: []byte, err: Error) { size: int has_size := true - if size64, err := file_size(f); err == nil { + if size64, serr := file_size(f); serr == nil { if i64(int(size64)) != size64 { size = int(size64) } - } else if err == .No_Size { + } else if serr == .No_Size { has_size = false } else { return @@ -135,6 +137,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d } } +@(require_results) write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index fc3cebaea..37f8f44de 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -6,7 +6,6 @@ import "base:runtime" import "core:io" import "core:mem" import "core:sync" -import "core:strings" import "core:time" import "core:unicode/utf16" import win32 "core:sys/windows" @@ -17,49 +16,6 @@ S_IWRITE :: 0o200 _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 -_file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() -} - -_temp_allocator_proc :: runtime.arena_allocator_proc - -@(private="file", thread_local) -_global_default_temp_allocator_arena: runtime.Arena - -_temp_allocator :: proc() -> runtime.Allocator { - return runtime.Allocator{ - procedure = _temp_allocator_proc, - data = &_global_default_temp_allocator_arena, - } -} - -@(require_results) -_temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { - temp = runtime.arena_temp_begin(&_global_default_temp_allocator_arena, loc) - return -} - -_temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) -} - -@(fini, private) -_destroy_temp_allocator_fini :: proc() { - runtime.arena_destroy(&_global_default_temp_allocator_arena) - _global_default_temp_allocator_arena = {} -} - -@(deferred_out=_temp_allocator_temp_end) -_TEMP_ALLOCATOR_GUARD :: #force_inline proc(ignore := false, loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - if ignore { - return {}, loc - } else { - return _temp_allocator_temp_begin(loc), loc - } -} - - - _File_Kind :: enum u8 { File, @@ -79,6 +35,20 @@ _File :: struct { p_mutex: sync.Mutex, // pread pwrite calls } +@(init) +init_std_files :: proc() { + stdin = new_file(uintptr(win32.GetStdHandle(win32.STD_INPUT_HANDLE)), "") + stdout = new_file(uintptr(win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)), "") + stderr = new_file(uintptr(win32.GetStdHandle(win32.STD_ERROR_HANDLE)), "") +} +@(fini) +fini_std_files :: proc() { + close(stdin) + close(stdout) + close(stderr) +} + + _handle :: proc(f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } @@ -162,11 +132,11 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { if handle == INVALID_HANDLE { return nil } - f := new(File, _file_allocator()) + f := new(File, file_allocator()) - f.impl.allocator = _file_allocator() + f.impl.allocator = file_allocator() f.impl.fd = rawptr(handle) - f.impl.name = strings.clone(name, f.impl.allocator) + f.impl.name, _ = clone_string(name, f.impl.allocator) f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator) handle := _handle(f) @@ -311,10 +281,10 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { to_read := min(win32.DWORD(length), MAX_RW) ok: win32.BOOL if f.impl.kind == .Console { - n, err := read_console(handle, p[total_read:][:to_read]) + n, cerr := read_console(handle, p[total_read:][:to_read]) total_read += n - if err != nil { - return i64(total_read), err + if cerr != nil { + return i64(total_read), cerr } } else { ok = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) @@ -583,9 +553,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - 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) if n == 0 { return "", _get_platform_error() @@ -798,14 +768,12 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, ferr = _flush(f) err = error_to_io_error(ferr) return - case .Close: + case .Close, .Destroy: ferr = _close(f) err = error_to_io_error(ferr) return case .Query: return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Query}) - case .Destroy: - return 0, .Empty } return 0, .Empty } diff --git a/core/os/os2/heap.odin b/core/os/os2/heap.odin index a07a0d618..e0cffaf0d 100644 --- a/core/os/os2/heap.odin +++ b/core/os/os2/heap.odin @@ -2,6 +2,7 @@ package os2 import "base:runtime" +@(require_results) heap_allocator :: proc() -> runtime.Allocator { return runtime.Allocator{ procedure = heap_allocator_proc, @@ -10,6 +11,7 @@ heap_allocator :: proc() -> runtime.Allocator { } +@(require_results) heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin new file mode 100644 index 000000000..59d845350 --- /dev/null +++ b/core/os/os2/internal_util.odin @@ -0,0 +1,128 @@ +//+private +package os2 + +import "base:intrinsics" +import "base:runtime" + + +// Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix +// parts which are split by the last "*" +@(require_results) +_prefix_and_suffix :: proc(pattern: string) -> (prefix, suffix: string, err: Error) { + for i in 0..= 0; i -= 1 { + if pattern[i] == '*' { + prefix, suffix = pattern[:i], pattern[i+1:] + break + } + } + return +} + +@(require_results) +clone_string :: proc(s: string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + buf := make([]byte, len(s), allocator) or_return + copy(buf, s) + return string(buf), nil +} + + +@(require_results) +clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstring, err: runtime.Allocator_Error) { + res = "" // do not use a `nil` cstring + buf := make([]byte, len(s)+1, allocator) or_return + copy(buf, s) + buf[len(s)] = 0 + return cstring(&buf[0]), nil +} + +@(require_results) +temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) { + return clone_to_cstring(s, temp_allocator()) +} + +@(require_results) +string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { + s := string(b) + i := 0 + for ; i < len(s); i += 1 { + if s[i] == 0 { + break + } + } + return s[:i] +} + +@(require_results) +concatenate_strings_from_buffer :: proc(buf: []byte, strings: ..string) -> string { + n := 0 + for s in strings { + (n < len(buf)) or_break + n += copy(buf[n:], s) + } + n = min(len(buf), n) + return string(buf[:n]) +} + +@(require_results) +concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + n := 0 + for s in strings { + n += len(s) + } + buf := make([]byte, n) or_return + n = 0 + for s in strings { + n += copy(buf[n:], s) + } + return string(buf), nil +} + + + +@(private="file") +random_string_seed: [2]u64 + +@(init, private="file") +init_random_string_seed :: proc() { + seed := u64(intrinsics.read_cycle_counter()) + s := &random_string_seed + s[0] = 0 + s[1] = (seed << 1) | 1 + _ = next_random(s) + s[1] += seed + _ = next_random(s) +} + +@(require_results) +next_random :: proc(r: ^[2]u64) -> u64 { + old_state := r[0] + r[0] = old_state * 6364136223846793005 + (r[1]|1) + xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081 + rot := (old_state >> 59) + return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63)) +} + +@(require_results) +random_string :: proc(buf: []byte) -> string { + @static digits := "0123456789" + + u := next_random(&random_string_seed) + + b :: 10 + i := len(buf) + for u >= b { + i -= 1 + buf[i] = digits[u % b] + u /= b + } + i -= 1 + buf[i] = digits[u % b] + return string(buf[i:]) +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index a3e7a5a96..27c3d6b0b 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,9 +2,11 @@ package os2 import "base:runtime" -Path_Separator :: _Path_Separator // OS-Specific -Path_List_Separator :: _Path_List_Separator // OS-Specific +Path_Separator :: _Path_Separator // OS-Specific +Path_Separator_String :: _Path_Separator_String // OS-Specific +Path_List_Separator :: _Path_List_Separator // OS-Specific +@(require_results) is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) } @@ -25,6 +27,7 @@ remove_all :: proc(path: string) -> Error { getwd :: get_working_directory +@(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { return _getwd(allocator) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 93de749b8..81176c219 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,13 +1,13 @@ //+private package os2 -import "core:strings" import "core:strconv" import "base:runtime" import "core:sys/unix" -_Path_Separator :: '/' -_Path_List_Separator :: ':' +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' _S_IFMT :: 0o170000 // Type of file mask _S_IFIFO :: 0o010000 // Named pipe (fifo) @@ -31,7 +31,8 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error { return .Invalid_Argument } - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + path_cstr := temp_cstring(path) or_return return _ok_or_error(unix.sys_mkdir(path_cstr, uint(perm & 0o777))) } @@ -71,6 +72,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return .Invalid_Argument } + TEMP_ALLOCATOR_GUARD() + // need something we can edit, and use to generate cstrings allocated: bool path_bytes: []u8 @@ -78,7 +81,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { allocated = true path_bytes = make([]u8, len(path) + 1) } else { - path_bytes = make([]u8, len(path) + 1, context.temp_allocator) + path_bytes = make([]u8, len(path) + 1, temp_allocator()) } // NULL terminate the byte slice to make it a valid cstring @@ -176,7 +179,8 @@ _remove_all :: proc(path: string) -> Error { return nil } - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + TEMP_ALLOCATOR_GUARD() + path_cstr := temp_cstring(path) or_return fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS) switch fd { @@ -202,7 +206,7 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) if res >= 0 { - return strings.string_from_null_terminated_ptr(&buf[0], len(buf)), nil + return string_from_null_terminated_bytes(buf[:]), nil } if res != -ERANGE { return "", _get_platform_error(res) @@ -213,7 +217,7 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { } _setwd :: proc(dir: string) -> Error { - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + dir_cstr := temp_cstring(dir) or_return return _ok_or_error(unix.sys_chdir(dir_cstr)) } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 7be4696d7..fcd1e3321 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -3,10 +3,10 @@ package os2 import win32 "core:sys/windows" import "base:runtime" -import "core:strings" -_Path_Separator :: '\\' -_Path_List_Separator :: ';' +_Path_Separator :: '\\' +_Path_Separator_String :: "\\" +_Path_List_Separator :: ';' _is_path_separator :: proc(c: byte) -> bool { return c == '\\' || c == '/' @@ -23,7 +23,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { - s = strings.concatenate({p, `\`}, _file_allocator()) or_return + s = concatenate({p, `\`}, file_allocator()) or_return allocated = true return } @@ -31,9 +31,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return p, false, nil } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - dir, err := stat(path, _temp_allocator()) + dir, err := stat(path, temp_allocator()) if err == nil { if dir.is_directory { return nil @@ -54,14 +54,14 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { if j > 1 { new_path, allocated := fix_root_directory(path[:j-1]) or_return defer if allocated { - delete(new_path, _file_allocator()) + delete(new_path, file_allocator()) } mkdir_all(new_path, perm) or_return } err = mkdir(path, perm) if err != nil { - dir1, err1 := lstat(path, _temp_allocator()) + dir1, err1 := lstat(path, temp_allocator()) if err1 == nil && dir1.is_directory { return nil } @@ -127,10 +127,10 @@ _fix_long_path_internal :: proc(path: string) -> string { return path } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() 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) n := len(path) r, w := 0, len(PREFIX) diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index 62f7ddf10..9254d6f8e 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,6 @@ package os2 +@(require_results) pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index a64522ac1..f79ad9165 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -3,6 +3,8 @@ package os2 import "core:time" import "base:runtime" +Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) + File_Info :: struct { fullpath: string, name: string, @@ -25,20 +27,27 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { delete(fi.fullpath, allocator) } +@(require_results) fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { + if f != nil && f.user_fstat != nil { + return f->user_fstat(allocator) + } return _fstat(f, allocator) } +@(require_results) stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _stat(name, allocator) } lstat :: stat_do_not_follow_links +@(require_results) stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) } +@(require_results) same_file :: proc(fi1, fi2: File_Info) -> bool { return _same_file(fi1, fi2) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index db929a719..dc287cafe 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -3,7 +3,6 @@ package os2 import "core:time" import "base:runtime" -import "core:strings" import "core:sys/unix" import "core:path/filepath" @@ -112,8 +111,9 @@ _fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Er } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath -_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return fd := unix.sys_open(name_cstr, _O_RDONLY) if fd < 0 { @@ -123,8 +123,9 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) return _fstat_internal(fd, allocator) } -_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := temp_cstring(name) or_return fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) if fd < 0 { @@ -137,9 +138,3 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - -_stat_internal :: proc(name: string) -> (s: _Stat, res: int) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) - res = unix.sys_stat(name_cstr, &s) - return -} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 154a5bbe3..03ad2052f 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -46,15 +46,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if name == "" { name = "." } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - p := win32.utf8_to_utf16(name, _temp_allocator()) + p := win32.utf8_to_utf16(name, temp_allocator()) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { 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) if n == 0 { return "", _get_platform_error() @@ -131,8 +131,8 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin if n == 0 { return "", _get_platform_error() } - _TEMP_ALLOCATOR_GUARD() - buf := make([]u16, max(n, 260)+1, _temp_allocator()) + TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_from_buf(buf[:n], allocator) } @@ -147,8 +147,8 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { if n == 0 { return nil, _get_platform_error() } - _TEMP_ALLOCATOR_GUARD() - buf := make([]u16, max(n, 260)+1, _temp_allocator()) + TEMP_ALLOCATOR_GUARD() + buf := make([]u16, max(n, 260)+1, temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) return _cleanpath_strip_prefix(buf[:n]), nil } diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index f12c2800e..3b3dbdd57 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -2,16 +2,96 @@ package os2 import "base:runtime" -create_temp_file :: proc(dir, pattern: string) -> (^File, Error) { - return _create_temp(dir, pattern) +@(private="file") +MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? + +// Creates a new temperatory file in the directory `dir`. +// +// Opens the file for reading and writing, with 0o666 permissions, and returns the new `^File`. +// The filename is generated by taking a pattern, and adding a randomized string to the end. +// If the pattern includes an "*", the random string replaces the last "*". +// If `dir` is an empty tring, `temp_directory()` will be used. +// +// The caller must `close` the file once finished with. +@(require_results) +create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { + TEMP_ALLOCATOR_GUARD() + dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix) or_return + + rand_buf: [32]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + f, err = open(name, {.Read, .Write, .Create, .Excl}, File_Mode(0o666)) + if err == .Exist { + close(f) + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return nil, err + } + return f, err + } } mkdir_temp :: make_directory_temp -make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - return _mkdir_temp(dir, pattern, allocator) +// Creates a new temporary directory in the directory `dir`, and returns the path of the new directory. +// +// The directory name is generated by taking a pattern, and adding a randomized string to the end. +// If the pattern includes an "*", the random string replaces the last "*". +// If `dir` is an empty tring, `temp_directory()` will be used. +@(require_results) +make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + prefix, suffix := _prefix_and_suffix(pattern) or_return + prefix = temp_join_path(dir, prefix) or_return + + rand_buf: [32]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + + attempts := 0 + for { + name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) + err = make_directory(name, 0o700) + if err == nil { + return clone_string(name, allocator) + } + if err == .Exist { + attempts += 1 + if attempts < MAX_ATTEMPTS { + continue + } + return "", err + } + if err == .Not_Exist { + if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { + return "", serr + } + } + return "", err + } + } temp_dir :: temp_directory +@(require_results) temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { return _temp_dir(allocator) } + + + +@(private="file") +temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { + if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { + return concatenate({dir, name}, temp_allocator(),) + } + + return concatenate({dir, Path_Separator_String, name}, temp_allocator()) +} diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index dd7ac5c97..92afcde47 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,16 +4,6 @@ package os2 import "base:runtime" -_create_temp :: proc(dir, pattern: string) -> (^File, Error) { - //TODO - return nil, nil -} - -_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - //TODO - return "", nil -} - _temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { //TODO return "", nil diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index c42da84f5..4c8ab9fb7 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -4,22 +4,14 @@ package os2 import "base:runtime" import win32 "core:sys/windows" -_create_temp :: proc(dir, pattern: string) -> (^File, Error) { - return nil, nil -} - -_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - return "", nil -} - _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { n := win32.GetTempPathW(0, nil) if n == 0 { return "", nil } - _TEMP_ALLOCATOR_GUARD() + TEMP_ALLOCATOR_GUARD() - b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) + b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) if n == 3 && b[1] == ':' && b[2] == '\\' { diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 0af461bf5..ca099f7ae 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -1,19 +1,19 @@ package os2 -import "core:strings" import "base:runtime" +@(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("LocalAppData", allocator) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: dir = get_env("HOME", allocator) if dir != "" { - dir = strings.concatenate({dir, "/Library/Caches"}, allocator) or_return + dir = concatenate({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) @@ -22,7 +22,7 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error if dir == "" { return } - dir = strings.concatenate({dir, "/.cache"}, allocator) or_return + dir = concatenate({dir, "/.cache"}, allocator) or_return } } if dir == "" { @@ -31,17 +31,18 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error return } +@(require_results) user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("AppData", allocator) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: dir = get_env("HOME", allocator) if dir != "" { - dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) or_return + dir = concatenate({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) @@ -50,7 +51,7 @@ user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Erro if dir == "" { return } - dir = strings.concatenate({dir, "/.config"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } } if dir == "" { @@ -59,6 +60,7 @@ user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Erro return } +@(require_results) user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { env := "HOME" #partial switch ODIN_OS { diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin new file mode 100644 index 000000000..e8e551340 --- /dev/null +++ b/core/os/os_netbsd.odin @@ -0,0 +1,780 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +import "base:runtime" +import "core:strings" +import "core:sys/unix" +import "core:c" + +Handle :: distinct i32 +File_Time :: distinct u64 +Errno :: distinct i32 + +INVALID_HANDLE :: ~Handle(0) + +ERROR_NONE: Errno : 0 /* No error */ +EPERM: Errno : 1 /* Operation not permitted */ +ENOENT: Errno : 2 /* No such file or directory */ +EINTR: Errno : 4 /* Interrupted system call */ +ESRCH: Errno : 3 /* No such process */ +EIO: Errno : 5 /* Input/output error */ +ENXIO: Errno : 6 /* Device not configured */ +E2BIG: Errno : 7 /* Argument list too long */ +ENOEXEC: Errno : 8 /* Exec format error */ +EBADF: Errno : 9 /* Bad file descriptor */ +ECHILD: Errno : 10 /* No child processes */ +EDEADLK: Errno : 11 /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM: Errno : 12 /* Cannot allocate memory */ +EACCES: Errno : 13 /* Permission denied */ +EFAULT: Errno : 14 /* Bad address */ +ENOTBLK: Errno : 15 /* Block device required */ +EBUSY: Errno : 16 /* Device busy */ +EEXIST: Errno : 17 /* File exists */ +EXDEV: Errno : 18 /* Cross-device link */ +ENODEV: Errno : 19 /* Operation not supported by device */ +ENOTDIR: Errno : 20 /* Not a directory */ +EISDIR: Errno : 21 /* Is a directory */ +EINVAL: Errno : 22 /* Invalid argument */ +ENFILE: Errno : 23 /* Too many open files in system */ +EMFILE: Errno : 24 /* Too many open files */ +ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ +ETXTBSY: Errno : 26 /* Text file busy */ +EFBIG: Errno : 27 /* File too large */ +ENOSPC: Errno : 28 /* No space left on device */ +ESPIPE: Errno : 29 /* Illegal seek */ +EROFS: Errno : 30 /* Read-only file system */ +EMLINK: Errno : 31 /* Too many links */ +EPIPE: Errno : 32 /* Broken pipe */ + +/* math software */ +EDOM: Errno : 33 /* Numerical argument out of domain */ +ERANGE: Errno : 34 /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +EAGAIN: Errno : 35 /* Resource temporarily unavailable */ +EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ +EINPROGRESS: Errno : 36 /* Operation now in progress */ +EALREADY: Errno : 37 /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ +EDESTADDRREQ: Errno : 39 /* Destination address required */ +EMSGSIZE: Errno : 40 /* Message too long */ +EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ +ENOPROTOOPT: Errno : 42 /* Protocol option not available */ +EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ +ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ +EOPNOTSUPP: Errno : 45 /* Operation not supported */ +EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ +EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ +EADDRINUSE: Errno : 48 /* Address already in use */ +EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN: Errno : 50 /* Network is down */ +ENETUNREACH: Errno : 51 /* Network is unreachable */ +ENETRESET: Errno : 52 /* Network dropped connection on reset */ +ECONNABORTED: Errno : 53 /* Software caused connection abort */ +ECONNRESET: Errno : 54 /* Connection reset by peer */ +ENOBUFS: Errno : 55 /* No buffer space available */ +EISCONN: Errno : 56 /* Socket is already connected */ +ENOTCONN: Errno : 57 /* Socket is not connected */ +ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ +ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ +ETIMEDOUT: Errno : 60 /* Operation timed out */ +ECONNREFUSED: Errno : 61 /* Connection refused */ + +ELOOP: Errno : 62 /* Too many levels of symbolic links */ +ENAMETOOLONG: Errno : 63 /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN: Errno : 64 /* Host is down */ +EHOSTUNREACH: Errno : 65 /* No route to host */ +ENOTEMPTY: Errno : 66 /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM: Errno : 67 /* Too many processes */ +EUSERS: Errno : 68 /* Too many users */ +EDQUOT: Errno : 69 /* Disc quota exceeded */ + +/* Network File System */ +ESTALE: Errno : 70 /* Stale NFS file handle */ +EREMOTE: Errno : 71 /* Too many levels of remote in path */ +EBADRPC: Errno : 72 /* RPC struct is bad */ +ERPCMISMATCH: Errno : 73 /* RPC version wrong */ +EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ +EPROGMISMATCH: Errno : 75 /* Program version wrong */ +EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ + +ENOLCK: Errno : 77 /* No locks available */ +ENOSYS: Errno : 78 /* Function not implemented */ + +EFTYPE: Errno : 79 /* Inappropriate file type or format */ +EAUTH: Errno : 80 /* Authentication error */ +ENEEDAUTH: Errno : 81 /* Need authenticator */ + +/* SystemV IPC */ +EIDRM: Errno : 82 /* Identifier removed */ +ENOMSG: Errno : 83 /* No message of desired type */ +EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +EILSEQ: Errno : 85 /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +ENOTSUP: Errno : 86 /* Not supported */ + +/* Realtime option errors */ +ECANCELED: Errno : 87 /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +EBADMSG: Errno : 88 /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +ENODATA: Errno : 89 /* No message available */ +ENOSR: Errno : 90 /* No STREAM resources */ +ENOSTR: Errno : 91 /* Not a STREAM */ +ETIME: Errno : 92 /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +ENOATTR: Errno : 93 /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +EMULTIHOP: Errno : 94 /* Multihop attempted */ +ENOLINK: Errno : 95 /* Link has been severed */ +EPROTO: Errno : 96 /* Protocol error */ + +/* Robust mutexes */ +EOWNERDEAD: Errno : 97 /* Previous owner died */ +ENOTRECOVERABLE: Errno : 98 /* State not recoverable */ + +ELAST: Errno : 98 /* Must equal largest errno */ + +/* end of errno */ + +O_RDONLY :: 0x000000000 +O_WRONLY :: 0x000000001 +O_RDWR :: 0x000000002 +O_CREATE :: 0x000000200 +O_EXCL :: 0x000000800 +O_NOCTTY :: 0x000008000 +O_TRUNC :: 0x000000400 +O_NONBLOCK :: 0x000000004 +O_APPEND :: 0x000000008 +O_SYNC :: 0x000000080 +O_ASYNC :: 0x000000040 +O_CLOEXEC :: 0x000400000 + +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +RTLD_GLOBAL :: 0x100 +RTLD_LOCAL :: 0x200 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x01000 +RTLD_NOLOAD :: 0x02000 + +F_GETPATH :: 15 + +MAX_PATH :: 1024 +MAXNAMLEN :: 511 + +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +dev_t :: u64 +ino_t :: u64 +nlink_t :: u32 +off_t :: i64 +mode_t :: u16 +pid_t :: u32 +uid_t :: u32 +gid_t :: u32 +blkcnt_t :: i64 +blksize_t :: i32 +fflags_t :: u32 +time_t :: i64 + +OS_Stat :: struct { + device_id: dev_t, + mode: mode_t, + _padding0: i16, + ino: ino_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + _padding1: i32, + rdev: dev_t, + + last_access: Unix_File_Time, + modified: Unix_File_Time, + status_change: Unix_File_Time, + birthtime: Unix_File_Time, + + size: off_t, + blocks: blkcnt_t, + block_size: blksize_t, + + flags: fflags_t, + gen: u32, + lspare: [2]u32, +} + +Dirent :: struct { + ino: ino_t, + reclen: u16, + namlen: u16, + type: u8, + name: [MAXNAMLEN + 1]byte, +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + +S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +foreign libc { + @(link_name="__errno") __errno_location :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(link_name="__lstat50") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="__fstat50") _unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- + + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} + +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. + +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +get_last_error :: proc "contextless" () -> int { + return int(__errno_location()^) +} + +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.int(mode)) + if handle == -1 { + return INVALID_HANDLE, Errno(get_last_error()) + } + return handle, ERROR_NONE +} + +close :: proc(fd: Handle) -> Errno { + result := _unix_close(fd) + if result == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Errno) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_read), ERROR_NONE +} + +write :: proc(fd: Handle, data: []byte) -> (int, Errno) { + if len(data) == 0 { + return 0, ERROR_NONE + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_written), ERROR_NONE +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + return -1, Errno(get_last_error()) + } + return res, ERROR_NONE +} + +file_size :: proc(fd: Handle) -> (i64, Errno) { + s, err := _fstat(fd) + if err != ERROR_NONE { + return -1, err + } + return s.size, ERROR_NONE +} + +rename :: proc(old_path, new_path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove_directory :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} + +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { + result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) + if result < 0 { + return 0, Errno(get_last_error()) + } + return int(result), ERROR_NONE +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { + s, err := _fstat(fd) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { + s, err := _stat(name) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +@private +_stat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_lstat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, Errno(get_last_error()) + } + return dirp, ERROR_NONE +} + +@private +_closedir :: proc(dirp: Dir) -> Errno { + rc := _unix_closedir(dirp) + if rc != 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +@private +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@private +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = Errno(get_last_error()) + return + } + err = ERROR_NONE + + if result == nil { + end_of_stream = true + return + } + + return +} + +@private +_readlink :: proc(path: string) -> (string, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", Errno(get_last_error()) + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + } + } + + return "", Errno{} +} + +absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { + buf: [MAX_PATH]byte + _, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) + if err != ERROR_NONE { + return "", err + } + + path := strings.clone_from_cstring(cstring(&buf[0])) + return path, err +} + +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Errno(get_last_error()) + } + defer _unix_free(path_ptr) + + path_cstr := transmute(cstring)path_ptr + path = strings.clone( string(path_cstr) ) + + return path, ERROR_NONE +} + +access :: proc(path: string, mask: int) -> (bool, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, Errno(get_last_error()) + } + return true, ERROR_NONE +} + +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + path_str := strings.clone_to_cstring(key, context.temp_allocator) + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +get_current_directory :: proc() -> string { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + #no_bounds_check for { + cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if Errno(get_last_error()) != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 do return Errno(get_last_error()) + return ERROR_NONE +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +current_thread_id :: proc "contextless" () -> int { + return cast(int) unix.pthread_self() +} + +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} + +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} + +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} + +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 do return page_size + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private) +_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} + +_alloc_command_line_arguments :: proc() -> []string { + res := make([]string, len(runtime.args__)) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 5e83c0e16..3bd62dfc7 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" diff --git a/core/os/stream.odin b/core/os/stream.odin index 25f31218c..9a168b95c 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -32,7 +32,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { @@ -46,7 +46,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = .EOF } case .Write_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { @@ -60,7 +60,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, case .Destroy: err = .Empty case .Query: - when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku { + when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query}) } else { return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 9beda5557..a4b27b027 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package filepath when ODIN_OS == .Darwin { @@ -61,7 +61,7 @@ when ODIN_OS == .Darwin { foreign libc { @(link_name="__error") __error :: proc() -> ^i32 --- } -} else when ODIN_OS == .OpenBSD { +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { @(private) foreign libc { @(link_name="__errno") __error :: proc() -> ^i32 --- diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin index 7915f8c32..e6199d86b 100644 --- a/core/prof/spall/spall_unix.odin +++ b/core/prof/spall/spall_unix.odin @@ -1,5 +1,5 @@ //+private -//+build darwin, freebsd, openbsd +//+build darwin, freebsd, openbsd, netbsd package spall // Only for types. diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin new file mode 100644 index 000000000..08f7cd6c9 --- /dev/null +++ b/core/sync/futex_netbsd.odin @@ -0,0 +1,74 @@ +//+private +package sync + +import "base:intrinsics" +import "core:time" +import "core:c" +import "core:sys/unix" + +foreign import libc "system:c" + +FUTEX_PRIVATE_FLAG :: 128 + +FUTEX_WAIT_PRIVATE :: 0 | FUTEX_PRIVATE_FLAG +FUTEX_WAKE_PRIVATE :: 1 | FUTEX_PRIVATE_FLAG + +EINTR :: 4 /* Interrupted system call */ +EAGAIN :: 35 /* Resource temporarily unavailable */ +ETIMEDOUT :: 60 /* Operation timed out */ + +Time_Spec :: struct { + time_sec: uint, + time_nsec: uint, +} + +get_last_error :: proc "contextless" () -> int { + foreign libc { + __errno :: proc() -> ^c.int --- + } + return int(__errno()^) +} + +_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0, 0, 0) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case: + _panic("futex_wait failure") + } + } + return true +} + +_futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, duration: time.Duration) -> bool { + if duration <= 0 { + return false + } + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{ + time_sec = cast(uint)(duration / 1e9), + time_nsec = cast(uint)(duration % 1e9), + }, 0, 0) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case ETIMEDOUT: + return false + case: + _panic("futex_wait_with_timeout failure") + } + } + return true +} + +_futex_signal :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1, 0, 0, 0) == -1 { + _panic("futex_wake_single failure") + } +} + +_futex_broadcast :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32)), 0, 0, 0) == -1 { + _panic("_futex_wake_all failure") + } +} diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin new file mode 100644 index 000000000..042e744e8 --- /dev/null +++ b/core/sync/primitives_netbsd.odin @@ -0,0 +1,8 @@ +//+private +package sync + +import "core:sys/unix" + +_current_thread_id :: proc "contextless" () -> int { + return cast(int) unix.pthread_self() +} diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index 2b8f9852f..63af853d0 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -67,8 +67,8 @@ init_cpu_features :: proc "c" () { try_set(&set, .os_xsave, 27, ecx1) try_set(&set, .rdrand, 30, ecx1) - when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { - // xgetbv is an illegal instruction under FreeBSD 13 & OpenBSD 7.1 + when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { + // xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10 // return before probing further cpu_features = set return @@ -133,4 +133,4 @@ init_cpu_name :: proc "c" () { brand = brand[:len(brand) - 1] } cpu_name = brand -} \ No newline at end of file +} diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_bsd.odin similarity index 89% rename from core/sys/info/platform_openbsd.odin rename to core/sys/info/platform_bsd.odin index 3a7c07570..e2273d253 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,3 +1,4 @@ +//+build openbsd, netbsd package sysinfo import sys "core:sys/unix" @@ -10,12 +11,16 @@ version_string_buf: [1024]u8 @(init, private) init_os_version :: proc () { - os_version.platform = .OpenBSD + when ODIN_OS == .NetBSD { + os_version.platform = .NetBSD + } else { + os_version.platform = .OpenBSD + } kernel_version_buf: [1024]u8 b := strings.builder_from_bytes(version_string_buf[:]) - // Retrieve kernel info using `sysctl`, e.g. OpenBSD + // Retrieve kernel info using `sysctl`, e.g. OpenBSD and NetBSD mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE} if !sys.sysctl(mib, &kernel_version_buf) { return @@ -68,4 +73,4 @@ init_ram :: proc() { if sys.sysctl(mib, &mem_size) { ram.total_ram = int(mem_size) } -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin new file mode 100644 index 000000000..afbbc321c --- /dev/null +++ b/core/sys/unix/pthread_netbsd.odin @@ -0,0 +1,102 @@ +package unix + +import "core:c" + +pthread_t :: distinct u64 + +SEM_T_SIZE :: 8 + +PTHREAD_CONDATTR_T_SIZE :: 16 +PTHREAD_MUTEXATTR_T_SIZE :: 16 +PTHREAD_RWLOCKATTR_T_SIZE :: 16 +PTHREAD_BARRIERATTR_T_SIZE :: 16 + +PTHREAD_COND_T_SIZE :: 40 +PTHREAD_MUTEX_T_SIZE :: 48 +PTHREAD_RWLOCK_T_SIZE :: 64 +PTHREAD_BARRIER_T_SIZE :: 48 +PTHREAD_ATTR_T_SIZE :: 16 + +pthread_cond_t :: struct #align(8) { + _: [PTHREAD_COND_T_SIZE] c.char, +} + +pthread_mutex_t :: struct #align(8) { + _: [PTHREAD_MUTEX_T_SIZE] c.char, +} + +pthread_rwlock_t :: struct #align(8) { + _: [PTHREAD_RWLOCK_T_SIZE] c.char, +} + +pthread_barrier_t :: struct #align(8) { + _: [PTHREAD_BARRIER_T_SIZE] c.char, +} + +pthread_attr_t :: struct #align(8) { + _: [PTHREAD_ATTR_T_SIZE] c.char, +} + +pthread_condattr_t :: struct #align(8) { + _: [PTHREAD_CONDATTR_T_SIZE] c.char, +} + +pthread_mutexattr_t :: struct #align(8) { + _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, +} + +pthread_rwlockattr_t :: struct #align(8) { + _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, +} + +pthread_barrierattr_t :: struct #align(8) { + _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, +} + +PTHREAD_MUTEX_NORMAL :: 0 +PTHREAD_MUTEX_ERRORCHECK :: 1 +PTHREAD_MUTEX_RECURSIVE :: 2 + +PTHREAD_CREATE_JOINABLE :: 0 +PTHREAD_CREATE_DETACHED :: 1 +PTHREAD_INHERIT_SCHED :: 0 +PTHREAD_EXPLICIT_SCHED :: 1 +PTHREAD_PROCESS_PRIVATE :: 0 +PTHREAD_PROCESS_SHARED :: 1 + +SCHED_NONE :: -1 +SCHED_OTHER :: 0 +SCHED_FIFO :: 1 +SCHED_RR :: 3 + +sched_param :: struct { + sched_priority: c.int, +} + +sem_t :: struct #align(16) { + _: [SEM_T_SIZE] c.char, +} + +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + +foreign import "system:pthread" + +@(default_calling_convention="c") +foreign pthread { + sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- + + sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- + sem_destroy :: proc(sem: ^sem_t) -> c.int --- + sem_post :: proc(sem: ^sem_t) -> c.int --- + sem_wait :: proc(sem: ^sem_t) -> c.int --- + sem_trywait :: proc(sem: ^sem_t) -> c.int --- + + pthread_yield :: proc() --- + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 4fe3c8dfa..5760560ee 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix foreign import "system:pthread" diff --git a/core/sys/unix/syscalls_netbsd.odin b/core/sys/unix/syscalls_netbsd.odin new file mode 100644 index 000000000..e92dc6d92 --- /dev/null +++ b/core/sys/unix/syscalls_netbsd.odin @@ -0,0 +1,3 @@ +package unix + +SYS___futex : uintptr : 166 diff --git a/core/sys/unix/sysctl_netbsd.odin b/core/sys/unix/sysctl_netbsd.odin new file mode 100644 index 000000000..ad89b9ad4 --- /dev/null +++ b/core/sys/unix/sysctl_netbsd.odin @@ -0,0 +1,44 @@ +package unix + +import "core:c" +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 --- +} + +sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { + mib := mib + result_size := c.size_t(size_of(T)) + res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0) + return res == 0 +} + +// See /usr/include/sys/sysctl.h for details +CTL_KERN :: 1 + KERN_OSTYPE :: 1 + KERN_OSRELEASE :: 2 + KERN_OSREV :: 3 + KERN_VERSION :: 4 +CTL_VM :: 2 +CTL_FS :: 3 +CTL_NET :: 4 +CTL_DEBUG :: 5 +CTL_HW :: 6 + HW_MACHINE :: 1 + HW_MODEL :: 2 + HW_NCPU :: 3 + HW_BYTEORDER :: 4 + HW_PHYSMEM :: 5 + HW_USERMEM :: 6 + HW_PAGESIZE :: 7 + HW_DISKNAMES :: 8 + HW_IOSTATS :: 9 + HW_MACHINE_ARCH :: 10 + HW_ALIGNBYTES :: 11 + HW_CNMAGIC :: 12 + HW_PHYSMEM64 :: 13 + HW_USERMEM64 :: 14 + HW_IOSTATNAMES :: 15 + HW_NCPUONLINE :: 16 diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin index 088dc378b..442202d36 100644 --- a/core/sys/unix/time_unix.odin +++ b/core/sys/unix/time_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix when ODIN_OS == .Darwin { @@ -9,11 +9,20 @@ when ODIN_OS == .Darwin { import "core:c" -@(default_calling_convention="c") -foreign libc { - clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- - sleep :: proc(seconds: c.uint) -> c.int --- - nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- +when ODIN_OS == .NetBSD { + @(default_calling_convention="c") + foreign libc { + @(link_name="__clock_gettime50") clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- + @(link_name="__nanosleep50") nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- + @(link_name="sleep") sleep :: proc(seconds: c.uint) -> c.int --- + } +} else { + @(default_calling_convention="c") + foreign libc { + clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- + sleep :: proc(seconds: c.uint) -> c.int --- + nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- + } } timespec :: struct { @@ -65,7 +74,6 @@ seconds_since_boot :: proc "c" () -> f64 { return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 } - inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { s, ns := nanoseconds / 1e9, nanoseconds % 1e9 requested := timespec{tv_sec=s, tv_nsec=ns} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 7c353fd33..acc0e05cb 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,4 +1,4 @@ -// +build linux, darwin, freebsd, openbsd, haiku +// +build linux, darwin, freebsd, openbsd, netbsd, haiku // +private package thread @@ -20,7 +20,7 @@ Thread_Os_Specific :: struct #align(16) { // It then waits for `start` to be called. // _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { - __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { + __unix_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) when ODIN_OS != .Darwin { @@ -82,7 +82,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0) - when ODIN_OS != .Haiku { + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) } @@ -95,7 +95,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Set thread priority. policy: i32 res: i32 - when ODIN_OS != .Haiku { + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { res = unix.pthread_attr_getschedpolicy(&attrs, &policy) assert(res == 0) } @@ -113,7 +113,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { assert(res == 0) thread.procedure = procedure - if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { + if unix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != 0 { free(thread, thread.creation_allocator) return nil } diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 1c46b5994..f3e0d6640 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package time import "core:sys/unix" diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 88ab9492c..c73595e99 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -17,7 +17,7 @@ #include #endif -#if defined(GB_SYSTEM_OPENBSD) +#if defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) #include #include #endif @@ -263,6 +263,14 @@ gb_internal void report_ram_info() { if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { gb_printf("%lld MiB\n", ram_amount / gb_megabytes(1)); } + #elif defined(GB_SYSTEM_NETBSD) + uint64_t ram_amount; + size_t val_size = sizeof(ram_amount); + + int mibs[] = { CTL_HW, HW_PHYSMEM64 }; + if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { + gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1)); + } #elif defined(GB_SYSTEM_OPENBSD) uint64_t ram_amount; size_t val_size = sizeof(ram_amount); @@ -998,13 +1006,17 @@ gb_internal void report_os_info() { gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch); return; } - #elif defined(GB_SYSTEM_OPENBSD) + #elif defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) struct utsname un; if (uname(&un) != -1) { gb_printf("%s %s %s %s\n", un.sysname, un.release, un.version, un.machine); } else { - gb_printf("OpenBSD: Unknown\n"); + #if defined(GB_SYSTEM_NETBSD) + gb_printf("NetBSD: Unknown\n"); + #else + gb_printf("OpenBSD: Unknown\n"); + #endif } #elif defined(GB_SYSTEM_FREEBSD) #define freebsd_version_buffer 129 diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ec7d03a84..f4e957479 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -18,6 +18,7 @@ enum TargetOsKind : u16 { TargetOs_essence, TargetOs_freebsd, TargetOs_openbsd, + TargetOs_netbsd, TargetOs_haiku, TargetOs_wasi, @@ -84,6 +85,7 @@ gb_global String target_os_names[TargetOs_COUNT] = { str_lit("essence"), str_lit("freebsd"), str_lit("openbsd"), + str_lit("netbsd"), str_lit("haiku"), str_lit("wasi"), @@ -644,6 +646,7 @@ struct QueryDataSetSettings { enum BuildModeKind { BuildMode_Executable, BuildMode_DynamicLibrary, + BuildMode_StaticLibrary, BuildMode_Object, BuildMode_Assembly, BuildMode_LLVM_IR, @@ -1020,6 +1023,13 @@ gb_global TargetMetrics target_openbsd_amd64 = { str_lit("x86_64-unknown-openbsd-elf"), }; +gb_global TargetMetrics target_netbsd_amd64 = { + TargetOs_netbsd, + TargetArch_amd64, + 8, 8, AMD64_MAX_ALIGNMENT, 16, + str_lit("x86_64-unknown-netbsd-elf"), +}; + gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, @@ -1127,6 +1137,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, + { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, @@ -1886,6 +1897,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #endif #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; + #elif defined(GB_SYSTEM_NETBSD) + metrics = &target_netbsd_amd64; #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) @@ -2272,7 +2285,12 @@ gb_internal bool init_build_paths(String init_filename) { } else if (build_context.metrics.os == TargetOs_darwin) { output_extension = STR_LIT("dylib"); } - } else if (build_context.build_mode == BuildMode_Object) { + } else if (build_context.build_mode == BuildMode_StaticLibrary) { + output_extension = STR_LIT("a"); + if (build_context.metrics.os == TargetOs_windows) { + output_extension = STR_LIT("lib"); + } + }else if (build_context.build_mode == BuildMode_Object) { // By default use a .o object extension. output_extension = STR_LIT("o"); @@ -2423,6 +2441,7 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_essence: case TargetOs_freebsd: case TargetOs_openbsd: + case TargetOs_netbsd: case TargetOs_haiku: gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); return false; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index f9947b88a..0222124d4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2440,32 +2440,6 @@ gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { return o->mode != Addressing_Variable && o->mode != Addressing_SoaVariable; } -gb_internal void check_old_for_or_switch_value_usage(Ast *expr) { - Entity *e = entity_of_node(expr); - if (e != nullptr && (e->flags & EntityFlag_OldForOrSwitchValue) != 0) { - GB_ASSERT(e->kind == Entity_Variable); - - ERROR_BLOCK(); - - if ((e->flags & EntityFlag_ForValue) != 0) { - Type *parent_type = type_deref(e->Variable.for_loop_parent_type); - - error(expr, "Assuming a for-in defined value is addressable as the iterable is passed by value has been disallowed."); - - if (is_type_map(parent_type)) { - error_line("\tSuggestion: Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); - } else { - error_line("\tSuggestion: Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); - } - } else { - GB_ASSERT((e->flags & EntityFlag_SwitchValue) != 0); - - error(expr, "Assuming a switch-in defined value is addressable as the iterable is passed by value has been disallowed."); - error_line("\tSuggestion: Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); - } - } -} - gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) { switch (op.kind) { case Token_And: { // Pointer address @@ -2493,7 +2467,10 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * { ERROR_BLOCK(); error(op, "Cannot take the pointer address of '%s'", str); - if (e != nullptr && (e->flags & EntityFlag_ForValue) != 0) { + if (e == nullptr) { + break; + } + if ((e->flags & EntityFlag_ForValue) != 0) { Type *parent_type = type_deref(e->Variable.for_loop_parent_type); if (parent_type != nullptr && is_type_string(parent_type)) { @@ -2503,9 +2480,17 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * } else { error_line("\tSuggestion: Did you want to pass the iterable value to the for statement by pointer to get addressable semantics?\n"); } + + if (is_type_map(parent_type)) { + error_line("\t Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\t Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); + } } - if (e != nullptr && (e->flags & EntityFlag_SwitchValue) != 0) { + if ((e->flags & EntityFlag_SwitchValue) != 0) { error_line("\tSuggestion: Did you want to pass the value to the switch statement by pointer to get addressable semantics?\n"); + + error_line("\t Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); } } break; @@ -2527,11 +2512,6 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * o->type = alloc_type_pointer(o->type); } } else { - if (ast_node_expect(node, Ast_UnaryExpr)) { - ast_node(ue, UnaryExpr, node); - check_old_for_or_switch_value_usage(ue->expr); - } - o->type = alloc_type_pointer(o->type); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 875503874..0267bdf80 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -501,7 +501,6 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O return nullptr; case Addressing_Variable: - check_old_for_or_switch_value_usage(lhs->expr); break; case Addressing_MapIndex: { @@ -523,9 +522,8 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O break; } - case Addressing_Context: { + case Addressing_Context: break; - } case Addressing_SoaVariable: break; @@ -1328,7 +1326,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } } - bool is_ptr = is_type_pointer(x.type); // NOTE(bill): Check for multiple defaults Ast *first_default = nullptr; @@ -1447,15 +1444,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } bool is_reference = is_addressed; - bool old_style = false; - - if (!is_reference && - is_ptr && - cc->list.count == 1 && - case_type != nullptr) { - is_reference = true; - old_style = true; - } if (cc->list.count > 1 || saw_nil) { case_type = nullptr; @@ -1477,9 +1465,6 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ if (!is_reference) { tag_var->flags |= EntityFlag_Value; } - if (old_style) { - tag_var->flags |= EntityFlag_OldForOrSwitchValue; - } add_entity(ctx, ctx->scope, lhs, tag_var); add_entity_use(ctx, lhs, tag_var); add_implicit_entity(ctx, stmt, tag_var); @@ -1618,7 +1603,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) auto entities = array_make(temporary_allocator(), 0, 2); bool is_map = false; bool is_bit_set = false; - bool use_by_reference_for_value = false; bool is_soa = false; bool is_reverse = rs->reverse; @@ -1679,7 +1663,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } } } - bool is_ptr = is_type_pointer(operand.type); Type *t = base_type(type_deref(operand.type)); switch (t->kind) { @@ -1719,32 +1702,27 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) break; case Type_EnumeratedArray: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->EnumeratedArray.elem); array_add(&vals, t->EnumeratedArray.index); break; case Type_Array: - if (is_ptr) use_by_reference_for_value = true; - if (!is_ptr) is_possibly_addressable = operand.mode == Addressing_Variable; + is_possibly_addressable = operand.mode == Addressing_Variable; array_add(&vals, t->Array.elem); array_add(&vals, t_int); break; case Type_DynamicArray: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->DynamicArray.elem); array_add(&vals, t_int); break; case Type_Slice: - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->Slice.elem); array_add(&vals, t_int); break; case Type_Map: - if (is_ptr) use_by_reference_for_value = true; is_map = true; array_add(&vals, t->Map.key); array_add(&vals, t->Map.value); @@ -1817,7 +1795,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Struct: if (t->Struct.soa_kind != StructSoa_None) { is_soa = true; - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->Struct.soa_elem); array_add(&vals, t_int); } @@ -1894,9 +1871,6 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) char const *idx_name = is_map ? "key" : is_bit_set ? "element" : "index"; error(token, "The %s variable '%.*s' cannot be made addressable", idx_name, LIT(str)); } - } else if (i == addressable_index && use_by_reference_for_value) { - entity->flags |= EntityFlag_OldForOrSwitchValue; - entity->flags &= ~EntityFlag_Value; } if (is_soa) { if (i == 0) { diff --git a/src/checker.cpp b/src/checker.cpp index 70ca4fc47..7cda0aa42 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1013,6 +1013,7 @@ gb_internal void init_universal(void) { {"FreeBSD", TargetOs_freebsd}, {"Haiku", TargetOs_haiku}, {"OpenBSD", TargetOs_openbsd}, + {"NetBSD", TargetOs_netbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, {"Freestanding", TargetOs_freestanding}, @@ -1043,6 +1044,7 @@ gb_internal void init_universal(void) { GlobalEnumValue values[BuildMode_COUNT] = { {"Executable", BuildMode_Executable}, {"Dynamic", BuildMode_DynamicLibrary}, + {"Static", BuildMode_StaticLibrary}, {"Object", BuildMode_Object}, {"Assembly", BuildMode_Assembly}, {"LLVM_IR", BuildMode_LLVM_IR}, diff --git a/src/entity.cpp b/src/entity.cpp index 60ca208ec..8a7417006 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -85,8 +85,6 @@ enum EntityFlag : u64 { EntityFlag_Require = 1ull<<50, EntityFlag_ByPtr = 1ull<<51, // enforce parameter is passed by pointer - EntityFlag_OldForOrSwitchValue = 1ull<<52, - EntityFlag_Overridden = 1ull<<63, }; diff --git a/src/gb/gb.h b/src/gb/gb.h index c55ff8a9a..58fbfa071 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -83,6 +83,10 @@ extern "C" { #ifndef GB_SYSTEM_OPENBSD #define GB_SYSTEM_OPENBSD 1 #endif + #elif defined(__NetBSD__) + #ifndef GB_SYSTEM_NETBSD + #define GB_SYSTEM_NETBSD 1 + #endif #elif defined(__HAIKU__) || defined(__haiku__) #ifndef GB_SYSTEM_HAIKU #define GB_SYSTEM_HAIKU 1 @@ -208,7 +212,7 @@ extern "C" { #endif #include // NOTE(bill): malloc on linux #include - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__) #include #endif #include @@ -250,6 +254,11 @@ extern "C" { #define lseek64 lseek #endif +#if defined(GB_SYSTEM_NETBSD) + #include + #define lseek64 lseek +#endif + #if defined(GB_SYSTEM_HAIKU) #include #include @@ -817,6 +826,13 @@ typedef struct gbAffinity { isize thread_count; isize threads_per_core; } gbAffinity; +#elif defined(GB_SYSTEM_NETBSD) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #elif defined(GB_SYSTEM_HAIKU) typedef struct gbAffinity { b32 is_accurate; @@ -3269,6 +3285,31 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { GB_ASSERT(0 <= core && core < a->core_count); return a->threads_per_core; } + +#elif defined(GB_SYSTEM_NETBSD) +#include + +void gb_affinity_init(gbAffinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} + #elif defined(GB_SYSTEM_HAIKU) #include diff --git a/src/linker.cpp b/src/linker.cpp index 33ca70462..c41f10593 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -212,10 +212,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_append_fmt(link_settings, " /PDB:\"%.*s\"", LIT(pdb_path)); } - if (build_context.no_crt) { - link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); - } else { - link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); + if (build_context.build_mode != BuildMode_StaticLibrary) { + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); + } else { + link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); + } } if (build_context.ODIN_DEBUG) { @@ -257,20 +259,31 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } + String linker_name = str_lit("link.exe"); switch (build_context.build_mode) { case BuildMode_Executable: link_settings = gb_string_append_fmt(link_settings, " /NOIMPLIB /NOEXP"); break; } + switch (build_context.build_mode) { + case BuildMode_StaticLibrary: + linker_name = str_lit("lib.exe"); + break; + default: + link_settings = gb_string_append_fmt(link_settings, " /incremental:no /opt:ref"); + break; + } + + result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s %.*s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%.*s " + "\"%.*s%.*s\" %s %.*s -OUT:\"%.*s\" %s " + "/nologo /subsystem:%.*s " "%.*s " "%.*s " "%s " "", - LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), + LIT(vs_exe_path), LIT(linker_name), object_files, LIT(res_path), LIT(output_filename), link_settings, LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), LIT(build_context.link_flags), @@ -458,6 +471,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); } + if (build_context.build_mode == BuildMode_StaticLibrary) { + compiler_error("TODO(bill): -build-mode:static on non-windows targets"); + } + // NOTE(dweiler): We use clang as a frontend for the linker as there are // other runtime and compiler support libraries that need to be linked in // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. diff --git a/src/main.cpp b/src/main.cpp index e76b0e380..4df6f97d5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -989,6 +989,8 @@ gb_internal bool parse_build_flags(Array args) { build_context.build_mode = BuildMode_DynamicLibrary; } else if (str == "obj" || str == "object") { build_context.build_mode = BuildMode_Object; + } else if (str == "static" || str == "lib") { + build_context.build_mode = BuildMode_StaticLibrary; } else if (str == "exe") { build_context.build_mode = BuildMode_Executable; } else if (str == "asm" || str == "assembly" || str == "assembler") { @@ -999,6 +1001,7 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("Unknown build mode '%.*s'\n", LIT(str)); gb_printf_err("Valid build modes:\n"); gb_printf_err("\tdll, shared, dynamic\n"); + gb_printf_err("\tlib, static\n"); gb_printf_err("\tobj, object\n"); gb_printf_err("\texe\n"); gb_printf_err("\tasm, assembly, assembler\n"); @@ -1637,6 +1640,7 @@ gb_internal void remove_temp_files(lbGenerator *gen) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: break; @@ -1655,6 +1659,7 @@ gb_internal void remove_temp_files(lbGenerator *gen) { if (!build_context.keep_object_files) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: for (String const &path : gen->output_object_paths) { gb_file_remove(cast(char const *)path.text); @@ -1833,6 +1838,9 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-build-mode:exe Builds as an executable."); print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); + print_usage_line(3, "-build-mode:static Builds as a statically linked library."); + print_usage_line(3, "-build-mode:lib Builds as an static library."); print_usage_line(3, "-build-mode:obj Builds as an object file."); print_usage_line(3, "-build-mode:object Builds as an object file."); print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); @@ -2866,6 +2874,7 @@ int main(int arg_count, char const **arg_ptr) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: i32 result = linker_stage(&linker_data); if (result) { @@ -2887,6 +2896,7 @@ int main(int arg_count, char const **arg_ptr) { if (lb_generate_code(gen)) { switch (build_context.build_mode) { case BuildMode_Executable: + case BuildMode_StaticLibrary: case BuildMode_DynamicLibrary: i32 result = linker_stage(gen); if (result) { diff --git a/src/path.cpp b/src/path.cpp index 93f6f5000..26ccb7cbf 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -341,7 +341,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) return ReadDirectory_None; } -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU) +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) || defined(GB_SYSTEM_HAIKU) #include diff --git a/src/threading.cpp b/src/threading.cpp index f2e0789f8..48c58e8f4 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -628,15 +628,23 @@ gb_internal void thread_set_name(Thread *t, char const *name) { pthread_setname_np(name); #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) pthread_set_name_np(t->posix_handle, name); +#elif defined(GB_SYSTEM_NETBSD) + pthread_setname_np(t->posix_handle, "%s", (void*)name); #else // TODO(bill): Test if this works pthread_setname_np(t->posix_handle, name); #endif } -#if defined(GB_SYSTEM_LINUX) -#include +#if defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_NETBSD) + #include +#ifdef GB_SYSTEM_LINUX + #include +#else + #include + #define SYS_futex SYS___futex +#endif gb_internal void futex_signal(Futex *addr) { int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); diff --git a/tests/core/Makefile b/tests/core/Makefile index 8996e2241..9026ed3d9 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -3,30 +3,32 @@ PYTHON=$(shell which python3) COMMON=-vet -strict-style COLLECTION=-collection:tests=.. -all: c_libc_test \ - compress_test \ - container_test \ - crypto_test \ - download_test_assets \ - encoding_test \ - filepath_test \ - fmt_test \ - hash_test \ - i18n_test \ - image_test \ - linalg_glsl_math_test \ - match_test \ - math_test \ - net_test \ - noise_test \ - os_exit_test \ - reflect_test \ - slice_test \ - strings_test \ - thread_test \ - runtime_test \ - time_test \ - fmt_test +all: all_bsd \ + net_test + +all_bsd: c_libc_test \ + compress_test \ + container_test \ + crypto_test \ + download_test_assets \ + encoding_test \ + filepath_test \ + fmt_test \ + hash_test \ + i18n_test \ + image_test \ + linalg_glsl_math_test \ + match_test \ + math_test \ + noise_test \ + os_exit_test \ + reflect_test \ + slice_test \ + strings_test \ + thread_test \ + runtime_test \ + time_test \ + fmt_test download_test_assets: $(PYTHON) download_assets.py @@ -101,4 +103,4 @@ runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime time_test: - $(ODIN) run time $(COMMON) -out:test_core_time \ No newline at end of file + $(ODIN) run time $(COMMON) -out:test_core_time diff --git a/tests/internal/Makefile b/tests/internal/Makefile index d17b9d012..09182cd23 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,6 +1,8 @@ ODIN=../../odin -all: rtti_test map_test pow_test 128_test asan_test string_compare_test +all: all_bsd asan_test + +all_bsd: rtti_test map_test pow_test 128_test string_compare_test rtti_test: $(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal @@ -18,4 +20,4 @@ asan_test: $(ODIN) run test_asan.odin -file -sanitize:address -debug string_compare_test: - $(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal \ No newline at end of file + $(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal diff --git a/vendor/ENet/unix.odin b/vendor/ENet/unix.odin index 05ce41e05..0bbfce20a 100644 --- a/vendor/ENet/unix.odin +++ b/vendor/ENet/unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package ENet // When we implement the appropriate bindings for Unix, the section separated diff --git a/vendor/directx/dxc/dxcdef_unix.odin b/vendor/directx/dxc/dxcdef_unix.odin index 12a682310..530d03ff0 100644 --- a/vendor/directx/dxc/dxcdef_unix.odin +++ b/vendor/directx/dxc/dxcdef_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd, netbsd package directx_dxc import "core:c"