Explorar el Código

Fill in most of os2/file_windows.odin

gingerBill hace 3 años
padre
commit
8b4b81fdeb

+ 3 - 0
core/os/os2/errors.odin

@@ -13,6 +13,9 @@ General_Error :: enum u32 {
 	Timeout,
 
 	Invalid_File,
+	Invalid_Path,
+
+	Unsupported,
 }
 
 Platform_Error :: struct {

+ 26 - 8
core/os/os2/file.odin

@@ -2,6 +2,7 @@ package os2
 
 import "core:io"
 import "core:time"
+import "core:runtime"
 
 File :: struct {
 	impl: _File,
@@ -31,6 +32,7 @@ File_Flag :: enum {
 	Sync,
 	Trunc,
 	Sparse,
+	Close_On_Exec,
 }
 
 O_RDONLY :: File_Flags{.Read}
@@ -137,24 +139,37 @@ symlink :: proc(old_name, new_name: string) -> Error {
 	return _symlink(old_name, new_name)
 }
 
-read_link :: proc(name: string) -> (string, Error) {
-	return _read_link(name)
+read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
+	return _read_link(name,allocator)
 }
 
 
-chdir :: proc(f: ^File) -> Error {
-	return _chdir(f)
+chdir :: proc(name: string) -> Error {
+	return _chdir(name)
 }
 
-chmod :: proc(f: ^File, mode: File_Mode) -> Error {
-	return _chmod(f, mode)
+chmod :: proc(name: string, mode: File_Mode) -> Error {
+	return _chmod(name, mode)
 }
 
-chown :: proc(f: ^File, uid, gid: int) -> Error {
-	return _chown(f, uid, gid)
+chown :: proc(name: string, uid, gid: int) -> Error {
+	return _chown(name, uid, gid)
+}
+
+fchdir :: proc(f: ^File) -> Error {
+	return _fchdir(f)
+}
+
+fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
+	return _fchmod(f, mode)
+}
+
+fchown :: proc(f: ^File, uid, gid: int) -> Error {
+	return _fchown(f, uid, gid)
 }
 
 
+
 lchown :: proc(name: string, uid, gid: int) -> Error {
 	return _lchown(name, uid, gid)
 }
@@ -163,6 +178,9 @@ lchown :: proc(name: string, uid, gid: int) -> Error {
 chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
 	return _chtimes(name, atime, mtime)
 }
+fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
+	return _fchtimes(f, atime, mtime)
+}
 
 exists :: proc(path: string) -> bool {
 	return _exists(path)

+ 444 - 29
core/os/os2/file_windows.odin

@@ -2,13 +2,19 @@
 package os2
 
 import "core:io"
-import "core:time"
+import "core:mem"
 import "core:runtime"
 import "core:strings"
+import "core:time"
+import "core:unicode/utf16"
 import win32 "core:sys/windows"
 
 INVALID_HANDLE :: ~uintptr(0)
 
+S_IWRITE :: 0o200
+_ERROR_BAD_NETPATH :: 53
+MAX_RW :: 1<<30
+
 _file_allocator :: proc() -> runtime.Allocator {
 	return heap_allocator()
 }
@@ -26,6 +32,10 @@ _File :: struct {
 	kind: _File_Kind,
 }
 
+_handle :: proc(f: ^File) -> win32.HANDLE {
+	return win32.HANDLE(_fd(f))
+}
+
 _get_platform_error :: proc() -> Error {
 	err := i32(win32.GetLastError())
 	if err != 0 {
@@ -34,8 +44,79 @@ _get_platform_error :: proc() -> Error {
 	return nil
 }
 
-_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
-	return nil, nil
+_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) {
+	if len(name) == 0 {
+		err = .Not_Exist
+		return
+	}
+
+	path := _fix_long_path(name)
+	access: u32
+	switch flags & {.Read, .Write} {
+	case {.Read}:         access = win32.FILE_GENERIC_READ
+	case {.Write}:        access = win32.FILE_GENERIC_WRITE
+	case {.Read, .Write}: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE
+	}
+
+	if .Create in flags {
+		access |= win32.FILE_GENERIC_WRITE
+	}
+	if .Append in flags {
+		access &~= win32.FILE_GENERIC_WRITE
+		access |= win32.FILE_APPEND_DATA
+	}
+	share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE)
+	sa: ^win32.SECURITY_ATTRIBUTES
+	if .Close_On_Exec not_in flags {
+		sa = &win32.SECURITY_ATTRIBUTES{}
+		sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
+		sa.bInheritHandle = true
+	}
+
+	create_mode: u32 = win32.OPEN_EXISTING
+	switch {
+	case flags & {.Create, .Excl} == {.Create, .Excl}:
+		create_mode = win32.CREATE_NEW
+	case flags & {.Create, .Trunc} == {.Create, .Trunc}:
+		create_mode = win32.CREATE_ALWAYS
+	case flags & {.Create} == {.Create}:
+		create_mode = win32.OPEN_ALWAYS
+	case flags & {.Trunc} == {.Trunc}:
+		create_mode = win32.TRUNCATE_EXISTING
+	}
+
+	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL
+	if perm & S_IWRITE == 0 {
+		attrs = win32.FILE_ATTRIBUTE_READONLY
+		if create_mode == win32.CREATE_ALWAYS {
+			// NOTE(bill): Open has just asked to create a file in read-only mode.
+			// If the file already exists, to make it akin to a *nix open call,
+			// the call preserves the existing permissions.
+			h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil)
+			if h == win32.INVALID_HANDLE {
+				switch e := win32.GetLastError(); e {
+				case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND:
+					// file does not exist, create the file
+				case 0:
+					return uintptr(h), nil
+				case:
+					return 0, Platform_Error{i32(e)}
+				}
+			}
+		}
+	}
+	h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil)
+	if h == win32.INVALID_HANDLE {
+		return 0, _get_platform_error()
+	}
+	return uintptr(h), nil
+}
+
+
+_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) {
+	flags := flags if flags != nil else {.Read}
+	handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return
+	return _new_file(handle, name), nil
 }
 
 _new_file :: proc(handle: uintptr, name: string) -> ^File {
@@ -48,11 +129,12 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File {
 	f.impl.name = strings.clone(name, context.allocator)
 	f.impl.wname = win32.utf8_to_wstring(name, context.allocator)
 
+	handle := _handle(f)
 	kind := _File_Kind.File
-	if m: u32; win32.GetConsoleMode(win32.HANDLE(fd), &m) {
+	if m: u32; win32.GetConsoleMode(handle, &m) {
 		kind = .Console
 	}
-	if win32.GetFileType(win32.HANDLE(fd)) == win32.FILE_TYPE_PIPE {
+	if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
 		kind = .Pipe
 	}
 	f.impl.kind = kind
@@ -95,9 +177,14 @@ _name :: proc(f: ^File) -> string {
 }
 
 _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
-	if f == nil {
-		return
+	handle := _handle(f)
+	if handle == win32.INVALID_HANDLE {
+		return 0, .Invalid_File
+	}
+	if f.impl.kind == .Pipe {
+		return 0, .Invalid_File
 	}
+
 	w: u32
 	switch whence {
 	case .Start:   w = win32.FILE_BEGIN
@@ -106,12 +193,8 @@ _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error
 	}
 	hi := i32(offset>>32)
 	lo := i32(offset)
-	ft := win32.GetFileType(win32.HANDLE(fd))
-	if ft == win32.FILE_TYPE_PIPE {
-		return 0, .Invalid_File
-	}
 
-	dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
+	dw_ptr := win32.SetFilePointer(handle, lo, &hi, w)
 	if dw_ptr == win32.INVALID_SET_FILE_POINTER {
 		return 0, _get_platform_error()
 	}
@@ -119,35 +202,192 @@ _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error
 }
 
 _read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
-	return
+	read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
+		if len(b) == 0 {
+			return 0, nil
+		}
+
+		BUF_SIZE :: 386
+		buf16: [BUF_SIZE]u16
+		buf8: [4*BUF_SIZE]u8
+
+		for n < len(b) && err == nil {
+			min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
+			max_read := u32(min(BUF_SIZE, min_read))
+			if max_read == 0 {
+				break
+			}
+
+			single_read_length: u32
+			ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
+			if !ok {
+				err = _get_platform_error()
+			}
+
+			buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
+			src := buf8[:buf8_len]
+
+			ctrl_z := false
+			for i := 0; i < len(src) && n+i < len(b); i += 1 {
+				x := src[i]
+				if x == 0x1a { // ctrl-z
+					ctrl_z = true
+					break
+				}
+				b[n] = x
+				n += 1
+			}
+			if ctrl_z || single_read_length < max_read {
+				break
+			}
+
+			// NOTE(bill): if the last two values were a newline, then it is expected that
+			// this is the end of the input
+			if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
+				break
+			}
+		}
+
+		return
+	}
+
+	handle := _handle(f)
+
+	single_read_length: win32.DWORD
+	total_read: int
+	length := len(p)
+
+	to_read := min(win32.DWORD(length), MAX_RW)
+
+	e: win32.BOOL
+	if f.impl.kind == .Console {
+		n, err := read_console(handle, p[total_read:][:to_read])
+		total_read += n
+		if err != nil {
+			return int(total_read), err
+		}
+	} else {
+		e = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil)
+	}
+	if single_read_length <= 0 || !e {
+		return int(total_read), _get_platform_error()
+	}
+	total_read += int(single_read_length)
+
+	return int(total_read), nil
 }
 
 _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
+	pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) {
+		buf := data
+		if len(buf) > MAX_RW {
+			buf = buf[:MAX_RW]
+
+		}
+		curr_offset := seek(f, offset, .Current) or_return
+		defer seek(f, curr_offset, .Start)
+
+		o := win32.OVERLAPPED{
+			OffsetHigh = u32(offset>>32),
+			Offset = u32(offset),
+		}
+
+		// TODO(bill): Determine the correct behaviour for consoles
+
+		h := _handle(f)
+		done: win32.DWORD
+		if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
+			err = _get_platform_error()
+			done = 0
+		}
+		n = int(done)
+		return
+	}
+	p, offset := p, offset
+	for len(p) > 0 {
+		m := pread(f, p, offset) or_return
+		n += m
+		p = p[m:]
+		offset += i64(m)
+	}
 	return
 }
 
 _read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
+	// TODO(bill)
 	return
 }
 
 _write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
-	return
+	if len(p) == 0 {
+		return
+	}
+
+	single_write_length: win32.DWORD
+	total_write: i64
+	length := i64(len(p))
+
+	handle := _handle(f)
+
+	for total_write < length {
+		remaining := length - total_write
+		to_write := win32.DWORD(min(i32(remaining), MAX_RW))
+
+		e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil)
+		if single_write_length <= 0 || !e {
+			n = int(total_write)
+			err = _get_platform_error()
+			return
+		}
+		total_write += i64(single_write_length)
+	}
+	return int(total_write), nil
 }
 
 _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
+	pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) {
+		buf := data
+		if len(buf) > MAX_RW {
+			buf = buf[:MAX_RW]
+
+		}
+		curr_offset := seek(f, offset, .Current) or_return
+		defer seek(f, curr_offset, .Start)
+
+		o := win32.OVERLAPPED{
+			OffsetHigh = u32(offset>>32),
+			Offset = u32(offset),
+		}
+
+		h := _handle(f)
+		done: win32.DWORD
+		if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
+			err = _get_platform_error()
+			done = 0
+		}
+		n = int(done)
+		return
+	}
+
+	p, offset := p, offset
+	for len(p) > 0 {
+		m := pwrite(f, p, offset) or_return
+		n += m
+		p = p[m:]
+		offset += i64(m)
+	}
 	return
 }
 
 _write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
+	// TODO(bill)
 	return
 }
 
 _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
-	if f == nil {
-		return
-	}
 	length: win32.LARGE_INTEGER
-	if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
+	handle := _handle(f)
+	if !win32.GetFileSizeEx(handle, &length) {
 		err = _get_platform_error()
 	}
 	n = i64(length)
@@ -156,10 +396,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) {
 
 
 _sync :: proc(f: ^File) -> Error {
-	return nil
+	return _flush(f)
 }
 
 _flush :: proc(f: ^File) -> Error {
+	handle := _handle(f)
+	if !win32.FlushFileBuffers(handle) {
+		return _get_platform_error()
+	}
 	return nil
 }
 
@@ -170,7 +414,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
 	curr_off := seek(f, 0, .Current) or_return
 	defer seek(f, curr_off, .Start)
 	seek(f, size, .Start) or_return
-	if !win32.SetEndOfFile(win32.HANDLE(fd)) {
+	handle := _handle(f)
+	if !win32.SetEndOfFile(handle) {
 		return _get_platform_error()
 	}
 	return nil
@@ -234,43 +479,213 @@ _link :: proc(old_name, new_name: string) -> Error {
 }
 
 _symlink :: proc(old_name, new_name: string) -> Error {
-	return nil
+	return .Unsupported
+}
+
+_open_sym_link :: proc(p: [^]u16) -> (handle: win32.HANDLE, err: Error) {
+	attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS)
+	attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT
+	handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil)
+	if handle == win32.INVALID_HANDLE {
+		return nil, _get_platform_error()
+	}
+	return
+
+}
+
+_normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: string, err: Error) {
+	has_prefix :: proc(p: []u16, str: string) -> bool {
+		if len(p) < len(str) {
+			return false
+		}
+		// assume ascii
+		for i in 0..<len(str) {
+			if p[i] != u16(str[i]) {
+				return false
+			}
+		}
+		return true
+	}
+	has_unc_prefix :: proc(p: []u16) -> bool {
+		return has_prefix(p, `\??\`)
+	}
+
+	if !has_unc_prefix(p) {
+		return win32.utf16_to_utf8(p, allocator), nil
+	}
+
+	ws := p[4:]
+	switch {
+	case len(ws) >= 2 && ws[1] == ':':
+		return win32.utf16_to_utf8(ws, allocator), nil
+	case has_prefix(ws, `UNC\`):
+		ws[3] = '\\' // override data in buffer
+		return win32.utf16_to_utf8(ws[3:], allocator), nil
+	}
+
+
+	handle := _open_sym_link(raw_data(p)) or_return
+	defer win32.CloseHandle(handle)
+
+	n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS)
+	if n == 0 {
+		return "", _get_platform_error()
+	}
+	buf := make([]u16, n+1, context.temp_allocator)
+	n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
+	if n == 0 {
+		return "", _get_platform_error()
+	}
+
+	ws = buf[:n]
+	if has_unc_prefix(ws) {
+		ws = ws[4:]
+		if len(ws) > 3 && has_prefix(ws, `UNC`) {
+			ws[2] = '\\'
+			return win32.utf16_to_utf8(ws[2:], allocator), nil
+		}
+		return win32.utf16_to_utf8(ws, allocator), nil
+	}
+	return "", .Invalid_Path
 }
 
-_read_link :: proc(name: string) -> (string, Error) {
+_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
+	MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024
+
+	@thread_local
+	rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
+
+	p := _fix_long_path(name)
+	handle := _open_sym_link(p) or_return
+	defer win32.CloseHandle(handle)
+
+	bytes_returned: u32
+	if !win32.DeviceIoControl(handle, win32.FSCTL_GET_REPARSE_POINT, nil, 0, &rdb_buf[0], len(rdb_buf)-1, &bytes_returned, nil) {
+		err = _get_platform_error()
+		return
+	}
+	mem.zero_slice(rdb_buf[:min(bytes_returned+1, len(rdb_buf))])
+
+
+	rdb := (^win32.REPARSE_DATA_BUFFER)(&rdb_buf[0])
+	switch rdb.ReparseTag {
+	case win32.IO_REPARSE_TAG_SYMLINK:
+		rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest)
+		pb := win32.wstring(&rb.PathBuffer)
+		pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
+		p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
+		if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 {
+			return win32.utf16_to_utf8(p, allocator), nil
+		}
+		return _normalize_link_path(p, allocator)
+
+	case win32.IO_REPARSE_TAG_MOUNT_POINT:
+		rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest)
+		pb := win32.wstring(&rb.PathBuffer)
+		pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0
+		p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength]
+		return _normalize_link_path(p, allocator)
+	}
+	// Path wasn't a symlink/junction but another reparse point kind
 	return "", nil
 }
 
 
-_chdir :: proc(f: ^File) -> Error {
+_fchdir :: proc(f: ^File) -> Error {
 	if f == nil {
 		return nil
 	}
-	if win32.SetCurrentDirectoryW(f.impl.wname) {
-		return nil
+	if !win32.SetCurrentDirectoryW(f.impl.wname) {
+		return _get_platform_error()
 	}
-	return _get_platform_error()
+	return nil
 }
 
-_chmod :: proc(f: ^File, mode: File_Mode) -> Error {
+_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
+	if f == nil {
+		return nil
+	}
+	d: win32.BY_HANDLE_FILE_INFORMATION
+	if !win32.GetFileInformationByHandle(_handle(f), &d) {
+		return _get_platform_error()
+	}
+	attrs := d.dwFileAttributes
+	if mode & S_IWRITE != 0 {
+		attrs &~= win32.FILE_ATTRIBUTE_READONLY
+	} else {
+		attrs |= win32.FILE_ATTRIBUTE_READONLY
+	}
+
+	info: win32.FILE_BASIC_INFO
+	info.FileAttributes = attrs
+	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
+		return _get_platform_error()
+	}
 	return nil
 }
 
-_chown :: proc(f: ^File, uid, gid: int) -> Error {
+_fchown :: proc(f: ^File, uid, gid: int) -> Error {
+	return .Unsupported
+}
+
+_chdir :: proc(name: string) -> Error {
+	p := _fix_long_path(name)
+	if !win32.SetCurrentDirectoryW(p) {
+		return _get_platform_error()
+	}
 	return nil
 }
 
+_chmod :: proc(name: string, mode: File_Mode) -> Error {
+	f := open(name, {.Write}) or_return
+	defer close(f)
+	return _fchmod(f, mode)
+}
+
+_chown :: proc(name: string, uid, gid: int) -> Error {
+	return .Unsupported
+}
 
 _lchown :: proc(name: string, uid, gid: int) -> Error {
-	return nil
+	return .Unsupported
 }
 
 
 _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+	f := open(name, {.Write}) or_return
+	defer close(f)
+	return _fchtimes(f, atime, mtime)
+}
+_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
+	if f == nil {
+		return nil
+	}
+	d: win32.BY_HANDLE_FILE_INFORMATION
+	if !win32.GetFileInformationByHandle(_handle(f), &d) {
+		return _get_platform_error()
+	}
+
+	to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
+		// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
+		return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
+	}
+
+	atime, mtime := atime, mtime
+	if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
+		atime = mtime
+	}
+
+	info: win32.FILE_BASIC_INFO
+	info.LastAccessTime = to_windows_time(atime)
+	info.LastWriteTime  = to_windows_time(mtime)
+	if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
+		return _get_platform_error()
+	}
 	return nil
 }
 
 
+
 _exists :: proc(path: string) -> bool {
 	wpath := _fix_long_path(path)
 	attribs := win32.GetFileAttributesW(wpath)

+ 5 - 0
core/sys/windows/kernel32.odin

@@ -29,6 +29,11 @@ foreign kernel32 {
 	SetHandleInformation :: proc(hObject: HANDLE,
 	                             dwMask: DWORD,
 	                             dwFlags: DWORD) -> BOOL ---
+	SetFileInformationByHandle :: proc(hFile:                HANDLE,
+	                                   FileInformationClass: FILE_INFO_BY_HANDLE_CLASS,
+	                                   lpFileInformation:    LPVOID,
+	                                   dwBufferSize:         DWORD) -> BOOL ---
+
 
 	AddVectoredExceptionHandler :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID ---
 	AddVectoredContinueHandler  :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID ---

+ 1 - 0
core/sys/windows/types.odin

@@ -191,6 +191,7 @@ FILE_WRITE_DATA: DWORD : 0x00000002
 FILE_APPEND_DATA: DWORD : 0x00000004
 FILE_WRITE_EA: DWORD : 0x00000010
 FILE_WRITE_ATTRIBUTES: DWORD : 0x00000100
+FILE_READ_ATTRIBUTES: DWORD : 0x000000080
 READ_CONTROL: DWORD : 0x00020000
 SYNCHRONIZE: DWORD : 0x00100000
 GENERIC_READ: DWORD : 0x80000000