aboutsummaryrefslogtreecommitdiff
path: root/core/os/os2
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2021-04-14 19:39:12 +0100
committergingerBill <bill@gingerbill.org>2021-04-14 19:39:12 +0100
commitebbc33fdb5e044e5feb010d6d3a8bde41f71a05f (patch)
tree29ccc30a22ef8082df00eb3f2dbcfb9ee73e789f /core/os/os2
parent3a4373641b68019149007f04a201965ee961f74e (diff)
Mockup of the new `package os` interface (incomplete and non-functioning)
Diffstat (limited to 'core/os/os2')
-rw-r--r--core/os/os2/doc.odin11
-rw-r--r--core/os/os2/env.odin43
-rw-r--r--core/os/os2/env_windows.odin80
-rw-r--r--core/os/os2/errors.odin126
-rw-r--r--core/os/os2/errors_windows.odin14
-rw-r--r--core/os/os2/file.odin158
-rw-r--r--core/os/os2/file_stream.odin98
-rw-r--r--core/os/os2/file_util.odin122
-rw-r--r--core/os/os2/file_windows.odin136
-rw-r--r--core/os/os2/heap.odin21
-rw-r--r--core/os/os2/heap_windows.odin107
-rw-r--r--core/os/os2/path.odin29
-rw-r--r--core/os/os2/path_windows.odin31
-rw-r--r--core/os/os2/pipe.odin5
-rw-r--r--core/os/os2/pipe_windows.odin13
-rw-r--r--core/os/os2/process.odin101
-rw-r--r--core/os/os2/stat.odin42
-rw-r--r--core/os/os2/stat_windows.odin373
-rw-r--r--core/os/os2/temp_file.odin14
-rw-r--r--core/os/os2/temp_file_windows.odin29
-rw-r--r--core/os/os2/user.odin68
21 files changed, 1621 insertions, 0 deletions
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..<len(env) {
+ if env[j] == '=' {
+ unset_env(env[0:j]);
+ break;
+ }
+ }
+ }
+}
+
+_environ :: proc(allocator := context.allocator) -> []string {
+ envs := win32.GetEnvironmentStringsW();
+ if envs == nil {
+ return nil;
+ }
+ defer win32.FreeEnvironmentStringsW(envs);
+
+ r := make([dynamic]string, 0, 50, allocator);
+ for from, i, p := 0, 0, envs; true; i += 1 {
+ c := (^u16)(uintptr(p) + uintptr(i*2))^;
+ if c == 0 {
+ if i <= from {
+ break;
+ }
+ w := mem.slice_ptr(mem.ptr_offset(p, from), i-from);
+
+ append(&r, win32.utf16_to_utf8(w, allocator));
+ from = i + 1;
+ }
+ }
+
+ return r[:];
+}
+
+
diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin
new file mode 100644
index 000000000..00cd600a8
--- /dev/null
+++ b/core/os/os2/errors.odin
@@ -0,0 +1,126 @@
+package os2
+
+Platform_Error_Min_Bits :: 32;
+
+Error :: enum u64 {
+ None = 0,
+
+ // General Errors
+ Invalid_Argument,
+
+ Permission_Denied,
+ Exist,
+ Not_Exist,
+ Closed,
+
+ // Timeout Errors
+ Timeout,
+
+ // I/O Errors
+ // EOF is the error returned by `read` when no more input is available
+ EOF,
+
+ // Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
+ Unexpected_EOF,
+
+ // Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
+ Short_Write,
+
+ // Invalid_Write means that a write returned an impossible count
+ Invalid_Write,
+
+ // Short_Buffer means that a read required a longer buffer than was provided
+ Short_Buffer,
+
+ // No_Progress is returned by some implementations of `io.Reader` when many calls
+ // to `read` have failed to return any data or error.
+ // This is usually a signed of a broken `io.Reader` implementation
+ No_Progress,
+
+ Invalid_Whence,
+ Invalid_Offset,
+ Invalid_Unread,
+
+ Negative_Read,
+ Negative_Write,
+ Negative_Count,
+ Buffer_Full,
+
+ // Platform Specific Errors
+ Platform_Minimum = 1<<Platform_Error_Min_Bits,
+}
+
+Path_Error :: struct {
+ op: string,
+ path: string,
+ err: Error,
+}
+
+Link_Error :: struct {
+ op: string,
+ old: string,
+ new: string,
+ err: Error,
+}
+
+path_error_delete :: proc(perr: Maybe(Path_Error)) {
+ if err, ok := perr.?; ok {
+ context.allocator = error_allocator();
+ delete(err.op);
+ delete(err.path);
+ }
+}
+
+link_error_delete :: proc(lerr: Maybe(Link_Error)) {
+ if err, ok := lerr.?; ok {
+ context.allocator = error_allocator();
+ delete(err.op);
+ delete(err.old);
+ delete(err.new);
+ }
+}
+
+
+
+is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
+ if ferr >= .Platform_Minimum {
+ err = i32(u64(ferr)>>Platform_Error_Min_Bits);
+ ok = true;
+ }
+ return;
+}
+
+error_from_platform_error :: proc(errno: i32) -> Error {
+ return Error(u64(errno) << Platform_Error_Min_Bits);
+}
+
+error_string :: proc(ferr: Error) -> string {
+ #partial switch ferr {
+ case .None: return "";
+ case .Invalid_Argument: return "invalid argument";
+ case .Permission_Denied: return "permission denied";
+ case .Exist: return "file already exists";
+ case .Not_Exist: return "file does not exist";
+ case .Closed: return "file already closed";
+ case .Timeout: return "i/o timeout";
+ case .EOF: return "eof";
+ case .Unexpected_EOF: return "unexpected eof";
+ case .Short_Write: return "short write";
+ case .Invalid_Write: return "invalid write result";
+ case .Short_Buffer: return "short buffer";
+ case .No_Progress: return "multiple read calls return no data or error";
+ case .Invalid_Whence: return "invalid whence";
+ case .Invalid_Offset: return "invalid offset";
+ case .Invalid_Unread: return "invalid unread";
+ case .Negative_Read: return "negative read";
+ case .Negative_Write: return "negative write";
+ case .Negative_Count: return "negative count";
+ case .Buffer_Full: return "buffer full";
+ }
+
+ if errno, ok := is_platform_error(ferr); ok {
+ return _error_string(errno);
+ }
+
+ return "unknown error";
+}
diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin
new file mode 100644
index 000000000..18e99b615
--- /dev/null
+++ b/core/os/os2/errors_windows.odin
@@ -0,0 +1,14 @@
+//+private
+package os2
+
+import win32 "core:sys/windows"
+
+_error_string :: proc(errno: i32) -> string {
+ e := win32.DWORD(errno);
+ if e == 0 {
+ return "";
+ }
+ // TODO(bill): _error_string for windows
+ // FormatMessageW
+ return "";
+}
diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin
new file mode 100644
index 000000000..d64855646
--- /dev/null
+++ b/core/os/os2/file.odin
@@ -0,0 +1,158 @@
+package os2
+
+import "core:io"
+import "core:time"
+
+Handle :: distinct uintptr;
+
+Seek_From :: enum {
+ Start = 0, // seek relative to the origin of the file
+ Current = 1, // seek relative to the current offset
+ End = 2, // seek relative to the end
+}
+
+File_Mode :: distinct u32;
+File_Mode_Dir :: File_Mode(1<<16);
+File_Mode_Named_Pipe :: File_Mode(1<<17);
+File_Mode_Device :: File_Mode(1<<18);
+File_Mode_Char_Device :: File_Mode(1<<19);
+File_Mode_Sym_Link :: File_Mode(1<<20);
+
+
+O_RDONLY :: int( 0);
+O_WRONLY :: int( 1);
+O_RDWR :: int( 2);
+O_APPEND :: int( 4);
+O_CREATE :: int( 8);
+O_EXCL :: int(16);
+O_SYNC :: int(32);
+O_TRUNC :: int(64);
+
+
+
+stdin: Handle = 0; // OS-Specific
+stdout: Handle = 1; // OS-Specific
+stderr: Handle = 2; // OS-Specific
+
+
+create :: proc(name: string) -> (Handle, Error) {
+ return _create(name);
+}
+
+open :: proc(name: string) -> (Handle, Error) {
+ return _open(name);
+}
+
+open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) {
+ return _open_file(name, flag, perm);
+}
+
+close :: proc(fd: Handle) -> Error {
+ return _close(fd);
+}
+
+name :: proc(fd: Handle, allocator := context.allocator) -> string {
+ return _name(fd);
+}
+
+seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
+ return _seek(fd, offset, whence);
+}
+
+read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+ return _read(fd, p);
+}
+
+read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+ return _read_at(fd, p, offset);
+}
+
+read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
+ return _read_from(fd, r);
+}
+
+write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
+ return _write(fd, p);
+}
+
+write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
+ return _write_at(fd, p, offset);
+}
+
+write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
+ return _write_to(fd, w);
+}
+
+file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
+ return _file_size(fd);
+}
+
+
+sync :: proc(fd: Handle) -> Error {
+ return _sync(fd);
+}
+
+flush :: proc(fd: Handle) -> Error {
+ return _flush(fd);
+}
+
+truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) {
+ return _truncate(fd, size);
+}
+
+remove :: proc(name: string) -> Maybe(Path_Error) {
+ return _remove(name);
+}
+
+rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) {
+ return _rename(old_path, new_path);
+}
+
+
+link :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+ return _link(old_name, new_name);
+}
+
+symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) {
+ return _symlink(old_name, new_name);
+}
+
+read_link :: proc(name: string) -> (string, Maybe(Path_Error)) {
+ return _read_link(name);
+}
+
+
+chdir :: proc(fd: Handle) -> Error {
+ return _chdir(fd);
+}
+
+chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
+ return _chmod(fd, mode);
+}
+
+chown :: proc(fd: Handle, uid, gid: int) -> Error {
+ return _chown(fd, uid, gid);
+}
+
+
+lchown :: proc(name: string, uid, gid: int) -> Error {
+ return _lchown(name, uid, gid);
+}
+
+
+chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) {
+ return _chtimes(name, atime, mtime);
+}
+
+exists :: proc(path: string) -> bool {
+ return _exists(path);
+}
+
+is_file :: proc(path: string) -> bool {
+ return _is_file(path);
+}
+
+is_dir :: proc(path: string) -> bool {
+ return _is_dir(path);
+}
+
diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin
new file mode 100644
index 000000000..6877faea4
--- /dev/null
+++ b/core/os/os2/file_stream.odin
@@ -0,0 +1,98 @@
+package os2
+
+import "core:io"
+
+file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
+ s.stream_data = rawptr(uintptr(fd));
+ s.stream_vtable = _file_stream_vtable;
+ return;
+}
+
+@(private)
+error_to_io_error :: proc(ferr: Error) -> io.Error {
+ #partial switch ferr {
+ case .None: return .None;
+ case .EOF: return .EOF;
+ case .Unexpected_EOF: return .Unexpected_EOF;
+ case .Short_Write: return .Short_Write;
+ case .Invalid_Write: return .Invalid_Write;
+ case .Short_Buffer: return .Short_Buffer;
+ case .No_Progress: return .No_Progress;
+ case .Invalid_Whence: return .Invalid_Whence;
+ case .Invalid_Offset: return .Invalid_Offset;
+ case .Invalid_Unread: return .Invalid_Unread;
+ case .Negative_Read: return .Negative_Read;
+ case .Negative_Write: return .Negative_Write;
+ case .Negative_Count: return .Negative_Count;
+ case .Buffer_Full: return .Buffer_Full;
+ }
+ return .Unknown;
+}
+
+
+@(private)
+_file_stream_vtable := &io.Stream_VTable{
+ impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = read(fd, p);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = read_at(fd, p, offset);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = write_to(fd, w);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = write(fd, p);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = write_at(fd, p, offset);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ ferr: Error;
+ n, ferr = read_from(fd, r);
+ err = error_to_io_error(ferr);
+ return;
+ },
+ impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) {
+ fd := Handle(uintptr(s.stream_data));
+ n, ferr := seek(fd, offset, Seek_From(whence));
+ err := error_to_io_error(ferr);
+ return n, err;
+ },
+ impl_size = proc(s: io.Stream) -> i64 {
+ fd := Handle(uintptr(s.stream_data));
+ sz, _ := file_size(fd);
+ return sz;
+ },
+ impl_flush = proc(s: io.Stream) -> io.Error {
+ fd := Handle(uintptr(s.stream_data));
+ ferr := flush(fd);
+ return error_to_io_error(ferr);
+ },
+ impl_close = proc(s: io.Stream) -> io.Error {
+ fd := Handle(uintptr(s.stream_data));
+ ferr := close(fd);
+ return error_to_io_error(ferr);
+ },
+};
diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin
new file mode 100644
index 000000000..435eba3ab
--- /dev/null
+++ b/core/os/os2/file_util.odin
@@ -0,0 +1,122 @@
+package os2
+
+import "core:mem"
+import "core:strconv"
+import "core:unicode/utf8"
+
+write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) {
+ return write(fd, transmute([]byte)s);
+}
+
+write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) {
+ return write(fd, []byte{b});
+}
+
+write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
+ if r < utf8.RUNE_SELF {
+ return write_byte(fd, byte(r));
+ }
+
+ b: [4]byte;
+ b, n = utf8.encode_rune(r);
+ return write(fd, b[:n]);
+}
+
+write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
+ wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
+ n^ += m;
+ if merr != nil {
+ err^ = merr;
+ return true;
+ }
+ return false;
+ }
+
+ if wrap(write_byte(fd, '\''), &n, &err) { return; }
+
+ switch r {
+ case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return; }
+ case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return; }
+ case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return; }
+ case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return; }
+ case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return; }
+ case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return; }
+ case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return; }
+ case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return; }
+ case:
+ if r < 32 {
+ if wrap(write_string(fd, "\\x"), &n, &err) { return; }
+ b: [2]byte;
+ s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil);
+ switch len(s) {
+ case 0: if wrap(write_string(fd, "00"), &n, &err) { return; }
+ case 1: if wrap(write_rune(fd, '0'), &n, &err) { return; }
+ case 2: if wrap(write_string(fd, s), &n, &err) { return; }
+ }
+ } else {
+ if wrap(write_rune(fd, r), &n, &err) { return; }
+ }
+ }
+ _ = wrap(write_byte(fd, '\''), &n, &err);
+ return;
+}
+
+
+write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (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;
+}
+