diff --git a/core/os/os2/doc.odin b/core/os/os2/doc.odin new file mode 100644 index 000000000..e413ef186 --- /dev/null +++ b/core/os/os2/doc.odin @@ -0,0 +1,11 @@ +// Package os provides a platform-independent interface to operating system functionality. +// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number. +// +// The package os interface is intended to be uniform across all operating systems. +// Features not generally available appear in the system-specific packages under core:sys/*. +// +// +// IMPORTANT NOTE from Bill: this is purely a mockup of what I want the new package os to be, and NON-FUNCTIONING. +// It is not complete but should give designers a better idea of the general interface and how to write things. +// This entire interface is subject to change. +package os2 diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin new file mode 100644 index 000000000..ae1752a10 --- /dev/null +++ b/core/os/os2/env.odin @@ -0,0 +1,43 @@ +package os2 + +// get_env retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: the value will be allocated with the supplied allocator +get_env :: proc(key: string, allocator := context.allocator) -> string { + value, _ := lookup_env(key, allocator); + return value; +} + +// lookup_env gets the value of the environment variable named by the key +// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true +// Otherwise the returned value will be empty and the boolean will be false +// NOTE: the value will be allocated with the supplied allocator +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _lookup_env(key, allocator); +} + +// set_env sets the value of the environment variable named by the key +// Returns true on success, false on failure +set_env :: proc(key, value: string) -> bool { + return _set_env(key, value); +} + +// unset_env unsets a single environment variable +// Returns true on success, false on failure +unset_env :: proc(key: string) -> bool { + return _unset_env(key); +} + +clear_env :: proc() { + _clear_env(); +} + + +// environ returns a copy of strings representing the environment, in the form "key=value" +// NOTE: the slice of strings and the strings with be allocated using the supplied allocator +environ :: proc(allocator := context.allocator) -> []string { + return _environ(allocator); +} + + diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin new file mode 100644 index 000000000..b6a73ad81 --- /dev/null +++ b/core/os/os2/env_windows.odin @@ -0,0 +1,80 @@ +//+private +package os2 + +import "core:mem" +import win32 "core:sys/windows" + +_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + if key == "" { + return; + } + wkey := win32.utf8_to_wstring(key); + b := make([dynamic]u16, 100, context.temp_allocator); + for { + n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))); + if n == 0 { + err := win32.GetLastError(); + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false; + } + } + + if n <= u32(len(b)) { + value = win32.utf16_to_utf8(b[:n], allocator); + found = true; + return; + } + + resize(&b, len(b)*2); + } +} + +_set_env :: proc(key, value: string) -> bool { + k := win32.utf8_to_wstring(key); + v := win32.utf8_to_wstring(value); + + return bool(win32.SetEnvironmentVariableW(k, v)); +} + +_unset_env :: proc(key: string) -> bool { + k := win32.utf8_to_wstring(key); + return bool(win32.SetEnvironmentVariableW(k, nil)); +} + +_clear_env :: proc() { + envs := environ(context.temp_allocator); + for env in envs { + for j in 1.. (n: int, err: Error) { + s := transmute([]byte)mem.Raw_Slice{data, len}; + return write(fd, s); +} + +read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { + s := transmute([]byte)mem.Raw_Slice{data, len}; + return read(fd, s); +} + + + +read_entire_file :: proc(name: string, allocator := context.allocator) -> ([]byte, Error) { + f, ferr := open(name); + if ferr != nil { + return nil, ferr; + } + defer close(f); + + size: int; + if size64, err := file_size(f); err == nil { + if i64(int(size64)) != size64 { + size = int(size64); + } + } + size += 1; // for EOF + + // TODO(bill): Is this correct logic? + total: int; + data := make([]byte, size, allocator); + for { + n, err := read(f, data[total:]); + total += n; + if err != nil { + if err == .EOF { + err = nil; + } + return data[:total], err; + } + } +} + +write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { + flags := O_WRONLY|O_CREATE; + if truncate { + flags |= O_TRUNC; + } + f, err := open_file(name, flags, perm); + if err != nil { + return err; + } + _, err = write(f, data); + if cerr := close(f); cerr != nil && err == nil { + err = cerr; + } + return err; +} + diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin new file mode 100644 index 000000000..97fe6b3d9 --- /dev/null +++ b/core/os/os2/file_windows.odin @@ -0,0 +1,136 @@ +//+private +package os2 + +import "core:io" +import "core:time" + +_create :: proc(name: string) -> (Handle, Error) { + return 0, .None; +} + +_open :: proc(name: string) -> (Handle, Error) { + return 0, .None; +} + +_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { + return 0, .None; +} + +_close :: proc(fd: Handle) -> Error { + return .None; +} + +_name :: proc(fd: Handle, allocator := context.allocator) -> string { + return ""; +} + +_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + return; +} + +_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + return; +} + +_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + return; +} + +_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { + return; +} + +_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + return; +} + +_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + return; +} + +_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { + return; +} + +_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { + return; +} + + +_sync :: proc(fd: Handle) -> Error { + return .None; +} + +_flush :: proc(fd: Handle) -> Error { + return .None; +} + +_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { + return nil; +} + +_remove :: proc(name: string) -> Maybe(Path_Error) { + return nil; +} + +_rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) { + return nil; +} + + +_link :: proc(old_name, new_name: string) -> Maybe(Link_Error) { + return nil; +} + +_symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) { + return nil; +} + +_read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { + return "", nil; +} + + +_chdir :: proc(fd: Handle) -> Error { + return .None; +} + +_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { + return .None; +} + +_chown :: proc(fd: Handle, uid, gid: int) -> Error { + return .None; +} + + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .None; +} + + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) { + return nil; +} + + +_exists :: proc(path: string) -> bool { + return false; +} + +_is_file :: proc(path: string) -> bool { + return false; +} + +_is_dir :: proc(path: string) -> bool { + return false; +} + + +_path_error_delete :: proc(perr: Maybe(Path_Error)) { + +} + +_link_error_delete :: proc(lerr: Maybe(Link_Error)) { + +} diff --git a/core/os/os2/heap.odin b/core/os/os2/heap.odin new file mode 100644 index 000000000..08605d568 --- /dev/null +++ b/core/os/os2/heap.odin @@ -0,0 +1,21 @@ +package os2 + +import "core:runtime" + +heap_allocator :: proc() -> runtime.Allocator { + return runtime.Allocator{ + procedure = heap_allocator_proc, + data = nil, + }; +} + + +heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr { + return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, flags, loc); +} + + +@(private) +error_allocator := heap_allocator; diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin new file mode 100644 index 000000000..e0e9c906a --- /dev/null +++ b/core/os/os2/heap_windows.odin @@ -0,0 +1,107 @@ +//+private +package os2 + +import "core:runtime" +import "core:mem" +import win32 "core:sys/windows" + +heap_alloc :: proc(size: int) -> rawptr { + return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, uint(size)); +} + +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + heap_free(ptr); + return nil; + } + if ptr == nil { + return heap_alloc(new_size); + } + + return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size)); +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return; + } + win32.HeapFree(win32.GetProcessHeap(), 0, ptr); +} + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> rawptr { + a := max(alignment, align_of(rawptr)); + space := size + a - 1; + + allocated_mem: rawptr; + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^; + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)); + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)); + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))); + + ptr := uintptr(aligned_mem); + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a); + diff := int(aligned_ptr - ptr); + if (size + diff) > space { + return nil; + } + + aligned_mem = rawptr(aligned_ptr); + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem; + + return aligned_mem; + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^); + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> rawptr { + if p == nil { + return nil; + } + return aligned_alloc(new_size, new_alignment, p); + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment); + + case .Free: + aligned_free(old_memory); + + case .Free_All: + // NOTE(tetra): Do nothing. + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment); + } + return aligned_resize(old_memory, old_size, size, alignment); + + case .Query_Features: + set := (^runtime.Allocator_Mode_Set)(old_memory); + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features}; + } + return set; + + case .Query_Info: + return nil; + } + + return nil; +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin new file mode 100644 index 000000000..eee2b3cee --- /dev/null +++ b/core/os/os2/path.odin @@ -0,0 +1,29 @@ +package os2 + +Path_Separator :: _Path_Separator; // OS-Specific +Path_List_Separator :: _Path_List_Separator; // OS-Specific + +is_path_separator :: proc(c: byte) -> bool { + return _is_path_separator(c); +} + +mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { + return _mkdir(name, perm); +} + +mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { + return _mkdir_all(path, perm); +} + +remove_all :: proc(path: string) -> Maybe(Path_Error) { + return _remove_all(path); +} + + + +getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + return _getwd(allocator); +} +setwd :: proc(dir: string) -> (err: Error) { + return _setwd(dir); +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin new file mode 100644 index 000000000..5056eb638 --- /dev/null +++ b/core/os/os2/path_windows.odin @@ -0,0 +1,31 @@ +//+private +package os2 + +_Path_Separator :: '\\'; +_Path_List_Separator :: ';'; + +_is_path_separator :: proc(c: byte) -> bool { + return c == '\\' || c == '/'; +} + +_mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { + return nil; +} + +_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { + // TODO(bill): _mkdir_all for windows + return nil; +} + +_remove_all :: proc(path: string) -> Maybe(Path_Error) { + // TODO(bill): _remove_all for windows + return nil; +} + +_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + return "", nil; +} + +_setwd :: proc(dir: string) -> (err: Error) { + return nil; +} diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin new file mode 100644 index 000000000..8bb46b303 --- /dev/null +++ b/core/os/os2/pipe.odin @@ -0,0 +1,5 @@ +package os2 + +pipe :: proc() -> (r, w: Handle, err: Error) { + return _pipe(); +} diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin new file mode 100644 index 000000000..68adb6c3b --- /dev/null +++ b/core/os/os2/pipe_windows.odin @@ -0,0 +1,13 @@ +//+private +package os2 + +import win32 "core:sys/windows" + +_pipe :: proc() -> (r, w: Handle, err: Error) { + p: [2]win32.HANDLE; + if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + return 0, 0, error_from_platform_error(i32(win32.GetLastError())); + } + return Handle(p[0]), Handle(p[1]), nil; +} + diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin new file mode 100644 index 000000000..f0060b54f --- /dev/null +++ b/core/os/os2/process.odin @@ -0,0 +1,101 @@ +package os2 + +import sync "core:sync/sync2" +import "core:time" + +args: []string; + +exit :: proc "contextless" (code: int) -> ! { + // +} + +get_uid :: proc() -> int { + return -1; +} + +get_euid :: proc() -> int { + return -1; +} + +get_gid :: proc() -> int { + return -1; +} + +get_egid :: proc() -> int { + return -1; +} + +get_pid :: proc() -> int { + return -1; +} + +get_ppid :: proc() -> int { + return -1; +} + + +Process :: struct { + pid: int, + handle: uintptr, + is_done: b32, + signal_mutex: sync.RW_Mutex, +} + + +Process_Attributes :: struct { + dir: string, + env: []string, + files: []Handle, + sys: ^Process_Attributes_OS_Specific, +} + +Process_Attributes_OS_Specific :: struct{}; + +Process_Error :: enum { + None, +} + +Process_State :: struct { + pid: int, + exit_code: int, + exited: bool, + success: bool, + system_time: time.Duration, + user_time: time.Duration, + sys: rawptr, +} + +Signal :: #type proc(); + +Kill: Signal = nil; +Interrupt: Signal = nil; + + +find_process :: proc(pid: int) -> (^Process, Process_Error) { + return nil, .None; +} + + +process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { + return nil, .None; +} + +process_release :: proc(p: ^Process) -> Process_Error { + return .None; +} + +process_kill :: proc(p: ^Process) -> Process_Error { + return .None; +} + +process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { + return .None; +} + +process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { + return {}, .None; +} + + + + diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin new file mode 100644 index 000000000..791948c8d --- /dev/null +++ b/core/os/os2/stat.odin @@ -0,0 +1,42 @@ +package os2 + +import "core:time" + +File_Info :: struct { + fullpath: string, + name: string, + size: i64, + mode: File_Mode, + is_dir: bool, + creation_time: time.Time, + modification_time: time.Time, + access_time: time.Time, +} + +file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { + for i := len(infos)-1; i >= 0; i -= 1 { + file_info_delete(infos[i], allocator); + } + delete(infos, allocator); +} + +file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { + delete(fi.fullpath, allocator); +} + +fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _fstat(fd, allocator); +} + +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _stat(name, allocator); +} + +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _lstat(name, allocator); +} + + +same_file :: proc(fi1, fi2: File_Info) -> bool { + return _same_file(fi1, fi2); +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin new file mode 100644 index 000000000..ed739b894 --- /dev/null +++ b/core/os/os2/stat_windows.odin @@ -0,0 +1,373 @@ +//+private +package os2 + +import "core:time" +import win32 "core:sys/windows" + +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + if fd == 0 { + return {}, Path_Error{err = .Invalid_Argument}; + } + context.allocator = allocator; + + path, err := _cleanpath_from_handle(fd); + if err != nil { + return {}, err; + } + + h := win32.HANDLE(fd); + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: + fi: File_Info; + fi.fullpath = path; + fi.name = basename(path); + fi.mode |= file_type_mode(h); + return fi, nil; + } + + return _file_info_from_get_file_information_by_handle(path, h); +} +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS); +} +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT); +} +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath; +} + + + +_stat_errno :: proc(errno: win32.DWORD) -> Path_Error { + return Path_Error{err = error_from_platform_error(i32(errno))}; +} + + +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) { + name := name; + if name == "" { + name = "."; + } + p := win32.utf8_to_utf16(name, context.temp_allocator); + buf := make([dynamic]u16, 100, allocator); + for { + n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil); + if n == 0 { + delete(buf); + return "", _stat_errno(win32.GetLastError()); + } + if n <= u32(len(buf)) { + return win32.utf16_to_utf8(buf[:n]), nil; + } + resize(&buf, len(buf)*2); + } + + return; +} + + +internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) { + if len(name) == 0 { + return {}, Path_Error{err = .Not_Exist}; + } + + context.allocator = allocator; + + + wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator); + fa: win32.WIN32_FILE_ATTRIBUTE_DATA; + ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa); + if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + // Not a symlink + return _file_info_from_win32_file_attribute_data(&fa, name); + } + + err := 0 if ok else win32.GetLastError(); + + if err == win32.ERROR_SHARING_VIOLATION { + fd: win32.WIN32_FIND_DATAW; + sh := win32.FindFirstFileW(wname, &fd); + if sh == win32.INVALID_HANDLE_VALUE { + e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + return; + } + win32.FindClose(sh); + + return _file_info_from_win32_find_data(&fd, name); + } + + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil); + if h == win32.INVALID_HANDLE_VALUE { + e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + return; + } + defer win32.CloseHandle(h); + return _file_info_from_get_file_information_by_handle(name, h); +} + + +_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { + buf := buf; + N := 0; + for c, i in buf { + if c == 0 { break; } + N = i+1; + } + buf = buf[:N]; + + if len(buf) >= 4 { + if buf[0] == '\\' && + buf[1] == '\\' && + buf[2] == '?' && + buf[3] == '\\' { + buf = buf[4:]; + } + } + return buf; +} + + +_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { + if fd == 0 { + return "", Path_Error{err = .Invalid_Argument}; + } + h := win32.HANDLE(fd); + + MAX_PATH := win32.DWORD(260) + 1; + buf: []u16; + for { + buf = make([]u16, MAX_PATH, context.temp_allocator); + err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0); + switch err { + case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: + return "", _stat_errno(err); + case win32.ERROR_NOT_ENOUGH_MEMORY: + MAX_PATH = MAX_PATH*2 + 1; + continue; + } + break; + } + return _cleanpath_from_buf(buf), nil; +} + +_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { + if fd == 0 { + return nil, Path_Error{err = .Invalid_Argument}; + } + h := win32.HANDLE(fd); + + MAX_PATH := win32.DWORD(260) + 1; + buf: []u16; + for { + buf = make([]u16, MAX_PATH, context.temp_allocator); + err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0); + switch err { + case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: + return nil, _stat_errno(err); + case win32.ERROR_NOT_ENOUGH_MEMORY: + MAX_PATH = MAX_PATH*2 + 1; + continue; + } + break; + } + return _cleanpath_strip_prefix(buf), nil; +} + +_cleanpath_from_buf :: proc(buf: []u16) -> string { + buf := buf; + buf = _cleanpath_strip_prefix(buf); + return win32.utf16_to_utf8(buf, context.allocator); +} + + +basename :: proc(name: string) -> (base: string) { + name := name; + if len(name) > 3 && name[:3] == `\\?` { + name = name[3:]; + } + + if len(name) == 2 && name[1] == ':' { + return "."; + } else if len(name) > 2 && name[1] == ':' { + name = name[2:]; + } + i := len(name)-1; + + for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 { + name = name[:i]; + } + for i -= 1; i >= 0; i -= 1 { + if name[i] == '/' || name[i] == '\\' { + name = name[i+1:]; + break; + } + } + return name; +} + + +file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE: + return File_Mode_Named_Pipe; + case win32.FILE_TYPE_CHAR: + return File_Mode_Device | File_Mode_Char_Device; + } + return 0; +} + + + +_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { + if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + mode |= 0o444; + } else { + mode |= 0o666; + } + + is_sym := false; + if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + is_sym = false; + } else { + is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT; + } + + if is_sym { + mode |= File_Mode_Sym_Link; + } else { + if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + mode |= 0o111 | File_Mode_Dir; + } + + if h != nil { + mode |= file_type_mode(h); + } + } + + return; +} + + +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)); + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)); + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)); + + fi.fullpath, e = full_path_from_name(name); + fi.name = basename(fi.fullpath); + + return; +} + + +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)); + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)); + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)); + + fi.fullpath, e = full_path_from_name(name); + fi.name = basename(fi.fullpath); + + return; +} + + +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) { + d: win32.BY_HANDLE_FILE_INFORMATION; + if !win32.GetFileInformationByHandle(h, &d) { + return {}, _stat_errno(win32.GetLastError()); + + } + + ti: win32.FILE_ATTRIBUTE_TAG_INFO; + if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { + err := win32.GetLastError(); + if err != win32.ERROR_INVALID_PARAMETER { + return {}, _stat_errno(err); + } + // Indicate this is a symlink on FAT file systems + ti.ReparseTag = 0; + } + + fi: File_Info; + + fi.fullpath = path; + fi.name = basename(path); + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)); + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)); + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)); + + return fi, nil; +} + +_is_abs :: proc(path: string) -> bool { + if len(path) > 0 && path[0] == '/' { + return true; + } + if len(path) > 2 { + switch path[0] { + case 'A'..'Z', 'a'..'z': + return path[1] == ':' && is_path_separator(path[2]); + } + } + return false; +} + +_fix_long_path :: proc(path: string) -> string { + if len(path) < 248 { + return path; + } + + if len(path) >= 2 && path[:2] == `\\` { + return path; + } + if !_is_abs(path) { + return path; + } + + prefix :: `\\?`; + + path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator); + copy(path_buf, prefix); + n := len(path); + r, w := 0, len(prefix); + for r < n { + switch { + case is_path_separator(path[r]): + r += 1; + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + r += 1; + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + return path; + case: + path_buf[w] = '\\'; + w += 1; + for ; r < n && !is_path_separator(path[r]); r += 1 { + path_buf[w] = path[r]; + w += 1; + } + } + } + + if w == len(`\\?\c:`) { + path_buf[w] = '\\'; + w += 1; + } + return string(path_buf[:w]); +} diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin new file mode 100644 index 000000000..4969a07d9 --- /dev/null +++ b/core/os/os2/temp_file.odin @@ -0,0 +1,14 @@ +package os2 + + +create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + return _create_temp(dir, pattern); +} + +mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + return _mkdir_temp(dir, pattern); +} + +temp_dir :: proc(allocator := context.allocator) -> string { + return _temp_dir(allocator); +} diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin new file mode 100644 index 000000000..19dca1b04 --- /dev/null +++ b/core/os/os2/temp_file_windows.odin @@ -0,0 +1,29 @@ +//+private +package os2 + +import win32 "core:sys/windows" + +_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + return 0, .None; +} + +_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + return "", .None; +} + +_temp_dir :: proc(allocator := context.allocator) -> string { + b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator); + for { + n := win32.GetTempPathW(u32(len(b)), raw_data(b)); + if n > u32(len(b)) { + resize(&b, int(n)); + continue; + } + if n == 3 && b[1] == ':' && b[2] == '\\' { + + } else if n > 0 && b[n-1] == '\\' { + n -= 1; + } + return win32.utf16_to_utf8(b[:n], allocator); + } +} diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin new file mode 100644 index 000000000..b23597387 --- /dev/null +++ b/core/os/os2/user.odin @@ -0,0 +1,68 @@ +package os2 + +import "core:strings" + +user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { + switch ODIN_OS { + case "windows": + dir = get_env("LocalAppData"); + if dir != "" { + dir = strings.clone(dir, allocator); + } + case "darwin": + dir = get_env("HOME"); + if dir != "" { + dir = strings.concatenate({dir, "/Library/Caches"}, allocator); + } + case: // All other UNIX systems + dir = get_env("XDG_CACHE_HOME"); + if dir == "" { + dir = get_env("HOME"); + if dir == "" { + return; + } + dir = strings.concatenate({dir, "/.cache"}, allocator); + } + } + is_defined = dir != ""; + return; +} + +user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { + switch ODIN_OS { + case "windows": + dir = get_env("AppData"); + if dir != "" { + dir = strings.clone(dir, allocator); + } + case "darwin": + dir = get_env("HOME"); + if dir != "" { + dir = strings.concatenate({dir, "/Library/Application Support"}, allocator); + } + case: // All other UNIX systems + dir = get_env("XDG_CACHE_HOME"); + if dir == "" { + dir = get_env("HOME"); + if dir == "" { + return; + } + dir = strings.concatenate({dir, "/.config"}, allocator); + } + } + is_defined = dir != ""; + return; +} + +user_home_dir :: proc() -> (dir: string, is_defined: bool) { + env := "HOME"; + switch ODIN_OS { + case "windows": + env = "USERPROFILE"; + } + if v := get_env(env); v != "" { + return v, true; + } + return "", false; +} +