From 8b4b81fdeb5e5367602855d5bedd83083118dfc1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 11:33:01 +0100 Subject: [PATCH] Fill in most of os2/file_windows.odin --- core/os/os2/errors.odin | 3 + core/os/os2/file.odin | 34 ++- core/os/os2/file_windows.odin | 473 +++++++++++++++++++++++++++++++-- core/sys/windows/kernel32.odin | 5 + core/sys/windows/types.odin | 1 + 5 files changed, 479 insertions(+), 37 deletions(-) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 6ddae74b1..b54b10cb2 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -13,6 +13,9 @@ General_Error :: enum u32 { Timeout, Invalid_File, + Invalid_Path, + + Unsupported, } Platform_Error :: struct { diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 0ed08c850..268631dd6 100644 --- a/core/os/os2/file.odin +++ b/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,23 +139,36 @@ 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) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 880305830..07117a15a 100644 --- a/core/os/os2/file_windows.odin +++ b/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 } -_read_link :: proc(name: string) -> (string, Error) { +_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.. 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, 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) { + if !win32.SetCurrentDirectoryW(f.impl.wname) { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + if f == nil { return nil } - return _get_platform_error() -} + 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 + } -_chmod :: proc(f: ^File, mode: File_Mode) -> Error { + 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) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index d95ff91c9..1f8927a16 100644 --- a/core/sys/windows/kernel32.odin +++ b/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 --- diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index d06cd287c..6770e7a95 100644 --- a/core/sys/windows/types.odin +++ b/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