Browse Source

Mockup of the new `package os` interface (incomplete and non-functioning)

gingerBill 4 years ago
parent
commit
ebbc33fdb5

+ 11 - 0
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

+ 43 - 0
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);
+}
+
+

+ 80 - 0
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[:];
+}
+
+

+ 126 - 0
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";
+}

+ 14 - 0
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 "";
+}

+ 158 - 0
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);
+}
+

+ 98 - 0
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);
+	},
+};

+ 122 - 0
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;
+}
+

+ 136 - 0
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)) {
+
+}

+ 21 - 0
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;

+ 107 - 0
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;
+}

+ 29 - 0
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);
+}

+ 31 - 0
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;
+}

+ 5 - 0
core/os/os2/pipe.odin

@@ -0,0 +1,5 @@
+package os2
+
+pipe :: proc() -> (r, w: Handle, err: Error) {
+	return _pipe();
+}

+ 13 - 0
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;
+}
+

+ 101 - 0
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;
+}
+
+
+
+

+ 42 - 0
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);
+}

+ 373 - 0
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]);
+}

+ 14 - 0
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);
+}

+ 29 - 0
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);
+	}
+}

+ 68 - 0
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;
+}
+