merge from upstream and convert to ^File types

This commit is contained in:
jason
2022-05-16 13:49:57 -04:00
276 changed files with 30247 additions and 7931 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:])})
path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:]) or_else ""})
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
+2 -2
View File
@@ -22,7 +22,7 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
if n <= u32(len(b)) {
value = win32.utf16_to_utf8(b[:n], allocator)
value, _ = win32.utf16_to_utf8(b[:n], allocator)
found = true
return
}
@@ -76,7 +76,7 @@ environ :: proc(allocator := context.allocator) -> []string {
if i <= from {
break
}
append(&r, win32.utf16_to_utf8(envs[from:i], allocator))
append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "")
from = i + 1
}
}
+1 -1
View File
@@ -365,7 +365,7 @@ get_current_directory :: proc(allocator := context.allocator) -> string {
win32.ReleaseSRWLockExclusive(&cwd_lock)
return win32.utf16_to_utf8(dir_buf_wstr, allocator)
return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
}
set_current_directory :: proc(path: string) -> (err: Errno) {
+4
View File
@@ -9,6 +9,10 @@ OS :: ODIN_OS
ARCH :: ODIN_ARCH
ENDIAN :: ODIN_ENDIAN
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
write_string :: proc(fd: Handle, str: string) -> (int, Errno) {
return write(fd, transmute([]byte)str)
}
+30 -42
View File
@@ -1,33 +1,35 @@
//+private
package os2
//import "core:runtime"
//import "core:mem"
import win32 "core:sys/windows"
import "core:runtime"
_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" {
return
}
wkey := win32.utf8_to_wstring(key)
// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
buf_len := win32.GetEnvironmentVariableW(wkey, nil, 0)
if buf_len == 0 {
return
}
buf := make([dynamic]u16, buf_len, context.temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, raw_data(buf), buf_len)
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
if win32.GetLastError() == win32.ERROR_ENVVAR_NOT_FOUND {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
value = ""
found = true
return
return "", true
}
b := make([]u16, n+1, _temp_allocator())
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
}
return "", false
}
value = win32.utf16_to_utf8(buf[:n], allocator)
value = win32.utf16_to_utf8(b[:n], allocator) or_else ""
found = true
return
}
@@ -36,21 +38,18 @@ _set_env :: proc(key, value: string) -> bool {
k := win32.utf8_to_wstring(key)
v := win32.utf8_to_wstring(value)
// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
return bool(win32.SetEnvironmentVariableW(k, v))
}
_unset_env :: proc(key: string) -> bool {
k := win32.utf8_to_wstring(key)
// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
return bool(win32.SetEnvironmentVariableW(k, nil))
}
_clear_env :: proc() {
envs := environ(context.temp_allocator)
envs := environ(_temp_allocator())
for env in envs {
#no_bounds_check for j in 1..<len(env) {
for j in 1..<len(env) {
if env[j] == '=' {
unset_env(env[0:j])
break
@@ -59,34 +58,23 @@ _clear_env :: proc() {
}
}
_environ :: proc(allocator := context.allocator) -> []string {
envs := ([^]u16)(win32.GetEnvironmentStringsW())
_environ :: proc(allocator: runtime.Allocator) -> []string {
envs := win32.GetEnvironmentStringsW()
if envs == nil {
return nil
}
defer win32.FreeEnvironmentStringsW(envs)
length := 0
n := 0
count_loop: for {
if envs[length] == 0 {
n += 1
if envs[length+1] == 0 {
break count_loop
}
}
length += 1
}
r := make([dynamic]string, 0, n, allocator)
for offset, i := 0, 0; i < length && len(r) < n; i += 1 {
c := envs[i]
r := make([dynamic]string, 0, 50, allocator)
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
wstr := envs[offset:i]
append(&r, win32.utf16_to_utf8(wstr, allocator))
i += 1
offset = i
if i <= from {
break
}
w := ([^]u16)(p)[from:i]
append(&r, win32.utf16_to_utf8(w, allocator) or_else "")
from = i + 1
}
}
+54 -54
View File
@@ -1,9 +1,10 @@
package os2
import "core:io"
import "core:runtime"
General_Error :: enum u32 {
Invalid_Argument,
None,
Permission_Denied,
Exist,
@@ -11,79 +12,78 @@ General_Error :: enum u32 {
Closed,
Timeout,
Invalid_File,
Invalid_Dir,
Invalid_Path,
Unsupported,
}
Platform_Error :: struct {
err: i32,
}
Platform_Error :: enum i32 {None=0}
Error :: union {
Error :: union #shared_nil {
General_Error,
io.Error,
runtime.Allocator_Error,
Platform_Error,
}
#assert(size_of(Error) == size_of(u64))
Link_Error :: struct {
op: string,
old: string,
new: string,
err: Error,
}
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) {
v := ferr.(Platform_Error) or_else {}
return v.err, v.err != 0
return i32(v), i32(v) != 0
}
error_string :: proc(ferr: Error) -> string {
@static general_error_strings := [General_Error]string{
.Invalid_Argument = "invalid argument",
.Permission_Denied = "permission denied",
.Exist = "file already exists",
.Not_Exist = "file does not exist",
.Closed = "file already closed",
.Timeout = "i/o timeout",
}
@static io_error_strings := [io.Error]string{
.None = "",
.EOF = "eof",
.Unexpected_EOF = "unexpected eof",
.Short_Write = "short write",
.Invalid_Write = "invalid write result",
.Short_Buffer = "short buffer",
.No_Progress = "multiple read calls return no data or error",
.Invalid_Whence = "invalid whence",
.Invalid_Offset = "invalid offset",
.Invalid_Unread = "invalid unread",
.Negative_Read = "negative read",
.Negative_Write = "negative write",
.Negative_Count = "negative count",
.Buffer_Full = "buffer full",
.Unknown = "unknown i/o error",
.Empty = "empty i/o error",
}
if ferr == nil {
return ""
}
switch err in ferr {
case General_Error: return general_error_strings[err]
case io.Error: return io_error_strings[err]
case Platform_Error: return _error_string(err.err)
switch e in ferr {
case General_Error:
switch e {
case .None: return ""
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 .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Unsupported: return "unsupported"
}
case io.Error:
switch e {
case .None: return ""
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"
case .Unknown, .Empty: //
}
case runtime.Allocator_Error:
switch e {
case .None: return ""
case .Out_Of_Memory: return "out of memory"
case .Invalid_Pointer: return "invalid allocator pointer"
case .Invalid_Argument: return "invalid allocator argument"
case .Mode_Not_Implemented: return "allocator mode not implemented"
}
case Platform_Error:
return _error_string(i32(e))
}
return "unknown error"
+1 -1
View File
@@ -130,7 +130,7 @@ EHWPOISON :: 133 /* Memory page has hardware error */
_get_platform_error :: proc(res: int) -> Error {
errno := unix.get_errno(res)
return Platform_Error{i32(errno)}
return Platform_Error(i32(errno))
}
_ok_or_error :: proc(res: int) -> Error {
+46
View File
@@ -12,3 +12,49 @@ _error_string :: proc(errno: i32) -> string {
// FormatMessageW
return ""
}
_get_platform_error :: proc() -> Error {
err := win32.GetLastError()
if err == 0 {
return nil
}
switch err {
case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION:
return .Permission_Denied
case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS:
return .Exist
case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND:
return .Not_Exist
case win32.ERROR_NO_DATA:
return .Closed
case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT:
return .Timeout
case win32.ERROR_NOT_SUPPORTED:
return .Unsupported
case
win32.ERROR_BAD_ARGUMENTS,
win32.ERROR_INVALID_PARAMETER,
win32.ERROR_NOT_ENOUGH_MEMORY,
win32.ERROR_INVALID_HANDLE,
win32.ERROR_NO_MORE_FILES,
win32.ERROR_LOCK_VIOLATION,
win32.ERROR_HANDLE_EOF,
win32.ERROR_BROKEN_PIPE,
win32.ERROR_CALL_NOT_IMPLEMENTED,
win32.ERROR_INSUFFICIENT_BUFFER,
win32.ERROR_INVALID_NAME,
win32.ERROR_LOCK_FAILED,
win32.ERROR_ENVVAR_NOT_FOUND,
win32.ERROR_OPERATION_ABORTED,
win32.ERROR_IO_PENDING,
win32.ERROR_NO_UNICODE_TRANSLATION:
// fallthrough
}
return Platform_Error(err)
}
+133 -101
View File
@@ -2,10 +2,11 @@ package os2
import "core:io"
import "core:time"
import "core:runtime"
Handle :: distinct uintptr
INVALID_HANDLE :: ~Handle(0)
File :: struct {
impl: _File,
}
Seek_From :: enum {
Start = 0, // seek relative to the origin of the file
@@ -20,106 +21,109 @@ File_Mode_Device :: File_Mode(1<<18)
File_Mode_Char_Device :: File_Mode(1<<19)
File_Mode_Sym_Link :: File_Mode(1<<20)
File_Mode_Perm :: File_Mode(0o777) // Unix permision bits
File_Flag :: enum u32 {
Read = 0,
Write = 1,
Append = 2,
Create = 3,
Excl = 4,
Sync = 5,
Trunc = 6,
Close_On_Exec = 7,
File_Flags :: distinct bit_set[File_Flag; uint]
File_Flag :: enum {
Read,
Write,
Append,
Create,
Excl,
Sync,
Trunc,
Sparse,
Close_On_Exec,
Unbuffered_IO,
}
File_Flags :: distinct bit_set[File_Flag; u32]
O_RDONLY :: File_Flags{.Read}
O_WRONLY :: File_Flags{.Write}
O_RDWR :: File_Flags{.Read, .Write}
O_APPEND :: File_Flags{.Append}
O_CREATE :: File_Flags{.Create}
O_EXCL :: File_Flags{.Excl}
O_SYNC :: File_Flags{.Sync}
O_TRUNC :: File_Flags{.Trunc}
O_RDONLY :: File_Flags{.Read}
O_WRONLY :: File_Flags{.Write}
O_RDWR :: File_Flags{.Read, .Write}
O_APPEND :: File_Flags{.Append}
O_CREATE :: File_Flags{.Create}
O_EXCL :: File_Flags{.Excl}
O_SYNC :: File_Flags{.Sync}
O_TRUNC :: File_Flags{.Trunc}
O_SPARSE :: File_Flags{.Sparse}
O_CLOEXEC :: File_Flags{.Close_On_Exec}
Std_Handle_Kind :: enum u8 {
stdin = 0,
stdout = 1,
stderr = 2,
stdin: ^File = nil // OS-Specific
stdout: ^File = nil // OS-Specific
stderr: ^File = nil // OS-Specific
create :: proc(name: string) -> (^File, Error) {
return open(name, {.Read, .Write, .Create}, File_Mode(0o777))
}
stdin: Handle = std_handle(.stdin)
stdout: Handle = std_handle(.stdout)
stderr: Handle = std_handle(.stderr)
std_handle :: proc(kind: Std_Handle_Kind) -> Handle {
return _std_handle(kind)
}
create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) {
return open(name, {.Read, .Write, .Create}, perm)
}
open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) {
flags := flags
if .Write not_in flags {
flags += {.Read}
}
open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) {
return _open(name, flags, perm)
}
close :: proc(fd: Handle) -> Error {
return _close(fd)
new_file :: proc(handle: uintptr, name: string) -> ^File {
return _new_file(handle, name)
}
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)
fd :: proc(f: ^File) -> uintptr {
return _fd(f)
}
sync :: proc(fd: Handle) -> Error {
return _sync(fd)
close :: proc(f: ^File) -> Error {
return _close(f)
}
flush :: proc(fd: Handle) -> Error {
return _flush(fd)
name :: proc(f: ^File) -> string {
return _name(f)
}
truncate :: proc(fd: Handle, size: i64) -> Error {
return _truncate(fd, size)
seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
return _seek(f, offset, whence)
}
read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return _read(f, p)
}
read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return _read_at(f, p, offset)
}
read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
return _read_from(f, r)
}
write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
return _write(f, p)
}
write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
return _write_at(f, p, offset)
}
write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
return _write_to(f, w)
}
file_size :: proc(f: ^File) -> (n: i64, err: Error) {
return _file_size(f)
}
sync :: proc(f: ^File) -> Error {
return _sync(f)
}
flush :: proc(f: ^File) -> Error {
return _flush(f)
}
truncate :: proc(f: ^File, size: i64) -> Error {
return _truncate(f, size)
}
remove :: proc(name: string) -> Error {
@@ -139,28 +143,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)
}
unlink :: proc(path: string) -> Error {
return _unlink(path)
read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) {
return _read_link(name,allocator)
}
chdir :: proc(fd: Handle) -> Error {
return _chdir(fd)
chdir :: proc(name: string) -> Error {
return _chdir(name)
}
chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
return _chmod(fd, mode)
chmod :: proc(name: string, mode: File_Mode) -> Error {
return _chmod(name, mode)
}
chown :: proc(fd: Handle, uid, gid: int) -> Error {
return _chown(fd, 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)
@@ -170,16 +182,36 @@ 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)
}
is_file :: proc(fd: Handle) -> bool {
return _is_file(fd)
is_file :: proc(path: string) -> bool {
return _is_file(path)
}
is_dir :: proc(fd: Handle) -> bool {
return _is_dir(fd)
is_dir :: proc(path: string) -> bool {
return _is_dir(path)
}
copy_file :: proc(dst_path, src_path: string) -> Error {
src := open(src_path) or_return
defer close(src)
info := fstat(src, _file_allocator()) or_return
defer file_info_delete(info, _file_allocator())
if info.is_dir {
return .Invalid_File
}
dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return
defer close(dst)
_, err := io.copy(to_writer(dst), to_reader(src))
return err
}
+141 -67
View File
@@ -4,13 +4,10 @@ package os2
import "core:io"
import "core:time"
import "core:strings"
import "core:strconv"
import "core:runtime"
import "core:sys/unix"
_std_handle :: proc(kind: Std_Handle_Kind) -> Handle {
return Handle(kind)
}
INVALID_HANDLE :: -1
_O_RDONLY :: 0o0
_O_WRONLY :: 0o1
@@ -31,7 +28,17 @@ _AT_FDCWD :: -100
_CSTRING_NAME_HEAP_THRESHOLD :: 512
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) {
_File :: struct {
name: string,
fd: int,
allocator: runtime.Allocator,
}
_file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
}
_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
@@ -51,63 +58,75 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro
flags_i |= (_O_TRUNC * int(.Trunc in flags))
flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags))
handle_i := unix.sys_open(name_cstr, flags_i, int(perm))
if handle_i < 0 {
return INVALID_HANDLE, _get_platform_error(handle_i)
fd := unix.sys_open(name_cstr, flags_i, int(perm))
if fd < 0 {
return nil, _get_platform_error(fd)
}
return Handle(handle_i), nil
return _new_file(uintptr(fd), name), nil
}
_close :: proc(fd: Handle) -> Error {
res := unix.sys_close(int(fd))
_new_file :: proc(fd: uintptr, _: string) -> ^File {
file := new(File, _file_allocator())
file.impl.fd = int(fd)
file.impl.allocator = _file_allocator()
file.impl.name = _get_full_path(file.impl.fd, file.impl.allocator)
return file
}
_destroy :: proc(f: ^File) -> Error {
if f == nil {
return nil
}
delete(f.impl.name, f.impl.allocator)
free(f, f.impl.allocator)
return nil
}
_close :: proc(f: ^File) -> Error {
res := unix.sys_close(f.impl.fd)
return _ok_or_error(res)
}
_name :: proc(fd: Handle, allocator := context.allocator) -> string {
// NOTE: Not sure how portable this really is
PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8
copy(buf[:], PROC_FD_PATH)
strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
realpath: string
err: Error
if realpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || realpath[0] != '/' {
return ""
_fd :: proc(f: ^File) -> uintptr {
if f == nil {
return ~uintptr(0)
}
return realpath
return uintptr(f.impl.fd)
}
_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
res := unix.sys_lseek(int(fd), offset, int(whence))
_name :: proc(f: ^File) -> string {
return f.impl.name if f != nil else ""
}
_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
res := unix.sys_lseek(f.impl.fd, offset, int(whence))
if res < 0 {
return -1, _get_platform_error(int(res))
}
return res, nil
}
_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
return 0, nil
}
n = unix.sys_read(int(fd), &p[0], len(p))
n = unix.sys_read(f.impl.fd, &p[0], len(p))
if n < 0 {
return -1, _get_platform_error(int(unix.get_errno(n)))
return -1, _get_platform_error(n)
}
return n, nil
}
_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := unix.sys_pread(int(fd), &b[0], len(b), offset)
m := unix.sys_pread(f.impl.fd, &b[0], len(b), offset)
if m < 0 {
return -1, _get_platform_error(m)
}
@@ -118,30 +137,30 @@ _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) {
_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
//TODO
return
}
_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
return 0, nil
}
n = unix.sys_write(int(fd), &p[0], uint(len(p)))
n = unix.sys_write(f.impl.fd, &p[0], uint(len(p)))
if n < 0 {
return -1, _get_platform_error(n)
}
return int(n), nil
}
_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := unix.sys_pwrite(int(fd), &b[0], len(b), offset)
m := unix.sys_pwrite(f.impl.fd, &b[0], len(b), offset)
if m < 0 {
return -1, _get_platform_error(m)
}
@@ -152,30 +171,30 @@ _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) {
_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
//TODO
return
}
_file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
s: OS_Stat = ---
res := unix.sys_fstat(int(fd), &s)
_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
s: _Stat = ---
res := unix.sys_fstat(f.impl.fd, &s)
if res < 0 {
return -1, _get_platform_error(res)
}
return s.size, nil
}
_sync :: proc(fd: Handle) -> Error {
return _ok_or_error(unix.sys_fsync(int(fd)))
_sync :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(f.impl.fd))
}
_flush :: proc(fd: Handle) -> Error {
return _ok_or_error(unix.sys_fsync(int(fd)))
_flush :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fsync(f.impl.fd))
}
_truncate :: proc(fd: Handle, size: i64) -> Error {
return _ok_or_error(unix.sys_ftruncate(int(fd), size))
_truncate :: proc(f: ^File, size: i64) -> Error {
return _ok_or_error(unix.sys_ftruncate(f.impl.fd, size))
}
_remove :: proc(name: string) -> Error {
@@ -184,13 +203,13 @@ _remove :: proc(name: string) -> Error {
delete(name_cstr)
}
handle_i := unix.sys_open(name_cstr, int(File_Flags.Read))
if handle_i < 0 {
return _get_platform_error(handle_i)
fd := unix.sys_open(name_cstr, int(File_Flags.Read))
if fd < 0 {
return _get_platform_error(fd)
}
defer unix.sys_close(handle_i)
defer unix.sys_close(fd)
if _is_dir(Handle(handle_i)) {
if _is_dir_fd(fd) {
return _ok_or_error(unix.sys_rmdir(name_cstr))
}
return _ok_or_error(unix.sys_unlink(name_cstr))
@@ -242,7 +261,7 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (
rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz)
if rc < 0 {
delete(buf)
return "", _get_platform_error(int(unix.get_errno(rc)))
return "", _get_platform_error(rc)
} else if rc == int(bufsz) {
bufsz *= 2
delete(buf)
@@ -269,18 +288,40 @@ _unlink :: proc(name: string) -> Error {
return _ok_or_error(unix.sys_unlink(name_cstr))
}
_chdir :: proc(fd: Handle) -> Error {
return _ok_or_error(unix.sys_fchdir(int(fd)))
_chdir :: proc(name: string) -> Error {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
return _ok_or_error(unix.sys_chdir(name_cstr))
}
_chmod :: proc(fd: Handle, mode: File_Mode) -> Error {
return _ok_or_error(unix.sys_fchmod(int(fd), int(mode)))
_fchdir :: proc(f: ^File) -> Error {
return _ok_or_error(unix.sys_fchdir(f.impl.fd))
}
_chown :: proc(fd: Handle, uid, gid: int) -> Error {
return _ok_or_error(unix.sys_fchown(int(fd), uid, gid))
_chmod :: proc(name: string, mode: File_Mode) -> Error {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
return _ok_or_error(unix.sys_chmod(name_cstr, int(mode)))
}
_fchmod :: proc(f: ^File, mode: File_Mode) -> Error {
return _ok_or_error(unix.sys_fchmod(f.impl.fd, int(mode)))
}
// NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
return _ok_or_error(unix.sys_chown(name_cstr, uid, gid))
}
// NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
@@ -289,6 +330,11 @@ _lchown :: proc(name: string, uid, gid: int) -> Error {
return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid))
}
// NOTE: will throw error without super user priviledges
_fchown :: proc(f: ^File, uid, gid: int) -> Error {
return _ok_or_error(unix.sys_fchown(f.impl.fd, uid, gid))
}
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
@@ -301,6 +347,14 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, &times, 0))
}
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
times := [2]Unix_File_Time {
{ atime._nsec, 0 },
{ mtime._nsec, 0 },
}
return _ok_or_error(unix.sys_utimensat(f.impl.fd, nil, &times, 0))
}
_exists :: proc(name: string) -> bool {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
@@ -309,18 +363,38 @@ _exists :: proc(name: string) -> bool {
return unix.sys_access(name_cstr, F_OK) == 0
}
_is_file :: proc(fd: Handle) -> bool {
s: OS_Stat
res := unix.sys_fstat(int(fd), &s)
_is_file :: proc(name: string) -> bool {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
s: _Stat
res := unix.sys_stat(name_cstr, &s)
return S_ISREG(s.mode)
}
_is_file_fd :: proc(fd: int) -> bool {
s: _Stat
res := unix.sys_fstat(fd, &s)
if res < 0 { // error
return false
}
return S_ISREG(s.mode)
}
_is_dir :: proc(fd: Handle) -> bool {
s: OS_Stat
res := unix.sys_fstat(int(fd), &s)
_is_dir :: proc(name: string) -> bool {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
}
s: _Stat
res := unix.sys_stat(name_cstr, &s)
return S_ISDIR(s.mode)
}
_is_dir_fd :: proc(fd: int) -> bool {
s: _Stat
res := unix.sys_fstat(fd, &s)
if res < 0 { // error
return false
}
@@ -330,7 +404,7 @@ _is_dir :: proc(fd: Handle) -> bool {
// Ideally we want to use the temp_allocator. PATH_MAX on Linux is commonly
// defined as 512, however, it is well known that paths can exceed that limit.
// So, in theory you could have a path larger than the entire temp_allocator's
// buffer. Therefor any large paths will use context.allocator.
// buffer. Therefor, any large paths will use context.allocator.
_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) {
if len(name) > _CSTRING_NAME_HEAP_THRESHOLD {
cname = strings.clone_to_cstring(name)
+30 -22
View File
@@ -2,12 +2,20 @@ package os2
import "core:io"
file_to_stream :: proc(fd: Handle) -> (s: io.Stream) {
s.stream_data = rawptr(uintptr(fd))
to_stream :: proc(f: ^File) -> (s: io.Stream) {
s.stream_data = f
s.stream_vtable = _file_stream_vtable
return
}
to_writer :: proc(f: ^File) -> (s: io.Writer) {
return {to_stream(f)}
}
to_reader :: proc(f: ^File) -> (s: io.Reader) {
return {to_stream(f)}
}
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
@@ -20,66 +28,66 @@ error_to_io_error :: proc(ferr: Error) -> io.Error {
@(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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = read(fd, p)
n, ferr = read(f, 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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = read_at(fd, p, offset)
n, ferr = read_at(f, 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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = write_to(fd, w)
n, ferr = write_to(f, 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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = write(fd, p)
n, ferr = write(f, 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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = write_at(fd, p, offset)
n, ferr = write_at(f, 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))
f := (^File)(s.stream_data)
ferr: Error
n, ferr = read_from(fd, r)
n, ferr = read_from(f, 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))
f := (^File)(s.stream_data)
n, ferr := seek(f, 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)
f := (^File)(s.stream_data)
sz, _ := file_size(f)
return sz
},
impl_flush = proc(s: io.Stream) -> io.Error {
fd := Handle(uintptr(s.stream_data))
ferr := flush(fd)
f := (^File)(s.stream_data)
ferr := flush(f)
return error_to_io_error(ferr)
},
impl_close = proc(s: io.Stream) -> io.Error {
fd := Handle(uintptr(s.stream_data))
ferr := close(fd)
f := (^File)(s.stream_data)
ferr := close(f)
return error_to_io_error(ferr)
},
}
+61 -83
View File
@@ -4,25 +4,25 @@ 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_string :: proc(f: ^File, s: string) -> (n: int, err: Error) {
return write(f, transmute([]byte)s)
}
write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) {
return write(fd, []byte{b})
write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) {
return write(f, []byte{b})
}
write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
if r < utf8.RUNE_SELF {
return write_byte(fd, byte(r))
return write_byte(f, byte(r))
}
b: [4]byte
b, n = utf8.encode_rune(r)
return write(fd, b[:n])
return write(f, b[:n])
}
write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
n^ += m
if merr != nil {
@@ -32,102 +32,78 @@ write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) {
return false
}
if wrap(write_byte(fd, '\''), &n, &err) { return }
if wrap(write_byte(f, '\''), &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 '\a': if wrap(write_string(f, "\\a"), &n, &err) { return }
case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return }
case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return }
case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return }
case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return }
case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return }
case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return }
case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return }
case:
if r < 32 {
if wrap(write_string(fd, "\\x"), &n, &err) { return }
if wrap(write_string(f, "\\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 }
case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
case 2: if wrap(write_string(f, s), &n, &err) { return }
}
} else {
if wrap(write_rune(fd, r), &n, &err) { return }
if wrap(write_rune(f, r), &n, &err) { return }
}
}
_ = wrap(write_byte(fd, '\''), &n, &err)
_ = wrap(write_byte(f, '\''), &n, &err)
return
}
write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
s := transmute([]byte)mem.Raw_Slice{data, len}
return write(fd, s)
return write(f, s)
}
read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) {
read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) {
s := transmute([]byte)mem.Raw_Slice{data, len}
return read(fd, s)
return read(f, s)
}
read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) {
if len(buf) < min {
return 0, .Short_Buffer
read_entire_file :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
f, ferr := open(name)
if ferr != nil {
return nil, ferr
}
for n < min && err == nil {
nn: int
nn, err = read(fd, buf[n:])
n += nn
defer close(f)
size: int
if size64, err := file_size(f); err == nil {
if i64(int(size64)) != size64 {
size = int(size64)
}
}
if n >= min {
err = nil
size += 1 // for EOF
// TODO(bill): Is this correct logic?
total: int
data = make([]byte, size, allocator) or_return
for {
n: int
n, err = read(f, data[total:])
total += n
if err != nil {
if err == .EOF {
err = nil
}
data = data[:total]
return
}
}
return
}
read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) {
return read_at_least(fd, buf, len(buf))
}
file_size_from_path :: proc(path: string) -> (length: i64, err: Error) {
fd := open(path, O_RDONLY, 0) or_return
defer close(fd)
return file_size(fd)
}
read_entire_file :: proc{
read_entire_file_from_path,
read_entire_file_from_handle,
}
read_entire_file_from_path :: proc(name: string, allocator := context.allocator) -> (data: []byte, err: Error) {
fd := open(name, {.Read}) or_return
defer close(fd)
return read_entire_file_from_handle(fd, allocator)
}
read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator) -> (data: []byte, err: Error) {
length := file_size(fd) or_return
if length <= 0 {
return nil, nil
}
if i64(int(length)) != length {
return nil, .Short_Buffer
}
data = make([]byte, int(length), allocator)
if data == nil {
return nil, .Short_Buffer
}
defer if err != nil {
delete(data, allocator)
}
bytes_read := read_full(fd, data) or_return
return data[:bytes_read], nil
}
write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error {
@@ -135,9 +111,11 @@ write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate
if truncate {
flags |= O_TRUNC
}
f := open(name, flags, perm) or_return
_, err := write(f, data)
f, err := open(name, flags, perm)
if err != nil {
return err
}
_, err = write(f, data)
if cerr := close(f); cerr != nil && err == nil {
err = cerr
}
+550 -242
View File
@@ -2,323 +2,455 @@
package os2
import "core:io"
import "core:mem"
import "core:sync"
import "core:runtime"
import "core:strings"
import "core:time"
import "core:unicode/utf16"
import win32 "core:sys/windows"
_get_platform_error :: proc() -> Error {
// TODO(bill): map some of these errors correctly
err := win32.GetLastError()
if err == 0 {
return nil
}
return Platform_Error{i32(err)}
INVALID_HANDLE :: ~uintptr(0)
S_IWRITE :: 0o200
_ERROR_BAD_NETPATH :: 53
MAX_RW :: 1<<30
_file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
}
_ok_or_error :: proc(ok: win32.BOOL) -> Error {
return nil if ok else _get_platform_error()
_temp_allocator :: proc() -> runtime.Allocator {
// TODO(bill): make this not depend on the context allocator
return context.temp_allocator
}
_std_handle :: proc(kind: Std_Handle_Kind) -> Handle {
get_handle :: proc(h: win32.DWORD) -> Handle {
fd := win32.GetStdHandle(h)
when size_of(uintptr) == 8 {
win32.SetHandleInformation(fd, win32.HANDLE_FLAG_INHERIT, 0)
}
return Handle(fd)
}
switch kind {
case .stdin: return get_handle(win32.STD_INPUT_HANDLE)
case .stdout: return get_handle(win32.STD_OUTPUT_HANDLE)
case .stderr: return get_handle(win32.STD_ERROR_HANDLE)
}
unreachable()
_File_Kind :: enum u8 {
File,
Console,
Pipe,
}
_open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) {
handle = INVALID_HANDLE
if len(path) == 0 {
_File :: struct {
fd: rawptr,
name: string,
wname: win32.wstring,
kind: _File_Kind,
allocator: runtime.Allocator,
rw_mutex: sync.RW_Mutex, // read write calls
p_mutex: sync.Mutex, // pread pwrite calls
}
_handle :: proc(f: ^File) -> win32.HANDLE {
return win32.HANDLE(_fd(f))
}
_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 & O_RDONLY|O_WRONLY|O_RDWR {
case O_RDONLY: access = win32.FILE_GENERIC_READ
case O_WRONLY: access = win32.FILE_GENERIC_WRITE
case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE
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 .Append in flags {
access &~= win32.FILE_GENERIC_WRITE
access |= win32.FILE_APPEND_DATA
}
if .Create in flags {
access |= win32.FILE_GENERIC_WRITE
}
share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE
sa: ^win32.SECURITY_ATTRIBUTES = nil
sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true}
if .Close_On_Exec in flags {
sa = &sa_inherit
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
create_mode: u32 = win32.OPEN_EXISTING
switch {
case flags&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL):
case flags & {.Create, .Excl} == {.Create, .Excl}:
create_mode = win32.CREATE_NEW
case flags&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC):
case flags & {.Create, .Trunc} == {.Create, .Trunc}:
create_mode = win32.CREATE_ALWAYS
case flags&O_CREATE == O_CREATE:
case flags & {.Create} == {.Create}:
create_mode = win32.OPEN_ALWAYS
case flags&O_TRUNC == O_TRUNC:
case flags & {.Trunc} == {.Trunc}:
create_mode = win32.TRUNCATE_EXISTING
case:
create_mode = win32.OPEN_EXISTING
}
wide_path := win32.utf8_to_wstring(path)
handle = Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil))
if handle == INVALID_HANDLE {
err = _get_platform_error()
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(e)
}
}
}
}
return
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
}
_close :: proc(fd: Handle) -> Error {
if fd == 0 {
return .Invalid_Argument
_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 {
if handle == INVALID_HANDLE {
return nil
}
hnd := win32.HANDLE(fd)
f := new(File, _file_allocator())
file_info: win32.BY_HANDLE_FILE_INFORMATION
_ok_or_error(win32.GetFileInformationByHandle(hnd, &file_info)) or_return
f.impl.allocator = _file_allocator()
f.impl.fd = rawptr(fd)
f.impl.name = strings.clone(name, f.impl.allocator)
f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator)
if file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
handle := _handle(f)
kind := _File_Kind.File
if m: u32; win32.GetConsoleMode(handle, &m) {
kind = .Console
}
if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE {
kind = .Pipe
}
f.impl.kind = kind
return f
}
_fd :: proc(f: ^File) -> uintptr {
if f == nil {
return INVALID_HANDLE
}
return uintptr(f.impl.fd)
}
_destroy :: proc(f: ^File) -> Error {
if f == nil {
return nil
}
return _ok_or_error(win32.CloseHandle(hnd))
a := f.impl.allocator
free(f.impl.wname, a)
delete(f.impl.name, a)
free(f, a)
return nil
}
_name :: proc(fd: Handle, allocator := context.allocator) -> string {
FILE_NAME_NORMALIZED :: 0x0
handle := win32.HANDLE(fd)
buf_len := win32.GetFinalPathNameByHandleW(handle, nil, 0, FILE_NAME_NORMALIZED)
if buf_len == 0 {
return ""
_close :: proc(f: ^File) -> Error {
if f == nil {
return nil
}
buf := make([]u16, buf_len, context.temp_allocator)
n := win32.GetFinalPathNameByHandleW(handle, raw_data(buf), buf_len, FILE_NAME_NORMALIZED)
return win32.utf16_to_utf8(buf[:n], allocator)
if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) {
return .Closed
}
return _destroy(f)
}
_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
new_offset: win32.LARGE_INTEGER
move_method: win32.DWORD
_name :: proc(f: ^File) -> string {
return f.impl.name if f != nil else ""
}
_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) {
handle := _handle(f)
if handle == win32.INVALID_HANDLE {
return 0, .Invalid_File
}
if f.impl.kind == .Pipe {
return 0, .Invalid_File
}
sync.guard(&f.impl.rw_mutex)
w: u32
switch whence {
case .Start: move_method = win32.FILE_BEGIN
case .Current: move_method = win32.FILE_CURRENT
case .End: move_method = win32.FILE_END
case .Start: w = win32.FILE_BEGIN
case .Current: w = win32.FILE_CURRENT
case .End: w = win32.FILE_END
}
ok := win32.SetFilePointerEx(win32.HANDLE(fd), win32.LARGE_INTEGER(offset), &new_offset, move_method)
ret = i64(new_offset)
if !ok {
err = .Invalid_Whence
hi := i32(offset>>32)
lo := i32(offset)
dw_ptr := win32.SetFilePointer(handle, lo, &hi, w)
if dw_ptr == win32.INVALID_SET_FILE_POINTER {
return 0, _get_platform_error()
}
return
return i64(hi)<<32 + i64(dw_ptr), nil
}
MAX_RW :: 1<<30
_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
if len(b) == 0 {
return 0, nil
}
@(private="file")
_read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
if len(b) == 0 {
return 0, nil
}
// TODO(bill): should this be moved to `_File` instead?
BUF_SIZE :: 386
buf16: [BUF_SIZE]u16
buf8: [4*BUF_SIZE]u8
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
}
for n < len(b) && err == nil {
max_read := u32(min(BUF_SIZE, len(b)/4))
single_read_length: u32
ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
if !ok {
err = _get_platform_error()
}
single_read_length: u32
err = _ok_or_error(win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil))
buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
src := buf8[:buf8_len]
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
}
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
// 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
}
b[n] = x
n += 1
}
if ctrl_z || single_read_length < len(buf16) {
break
}
return
}
return
}
_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
return 0, nil
}
handle := win32.HANDLE(fd)
m: u32
is_console := win32.GetConsoleMode(handle, &m)
handle := _handle(f)
single_read_length: win32.DWORD
total_read: int
length := len(p)
to_read := min(win32.DWORD(length), MAX_RW)
sync.shared_guard(&f.impl.rw_mutex) // multiple readers
e: win32.BOOL
if is_console {
n, err := _read_console(handle, p[total_read:][:to_read])
total_read += n
if err != nil {
return int(total_read), err
if sync.guard(&f.impl.p_mutex) {
to_read := min(win32.DWORD(length), MAX_RW)
ok: 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 {
ok = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil)
}
if single_read_length > 0 && ok {
total_read += int(single_read_length)
} else {
err = _get_platform_error()
}
} 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]
_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
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
}
b, offset := p, offset
for len(b) > 0 {
m := _pread(fd, b, offset) or_return
sync.guard(&f.impl.p_mutex)
p, offset := p, offset
for len(p) > 0 {
m := pread(f, p, offset) or_return
n += m
b = b[m:]
p = p[m:]
offset += i64(m)
}
return
}
_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) {
_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) {
// TODO(bill)
return
}
_pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]
}
curr_offset := seek(fd, offset, .Current) or_return
defer seek(fd, curr_offset, .Start)
o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32),
Offset = u32(offset),
_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) {
if len(p) == 0 {
return
}
// TODO(bill): Determine the correct behaviour for consoles
single_write_length: win32.DWORD
total_write: i64
length := i64(len(p))
h := win32.HANDLE(fd)
done: win32.DWORD
_ok_or_error(win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return
return int(done), nil
handle := _handle(f)
sync.guard(&f.impl.rw_mutex)
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
}
_pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
buf := data
if len(buf) > MAX_RW {
buf = buf[:MAX_RW]
_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(fd, offset, .Current) or_return
defer seek(fd, curr_offset, .Start)
}
curr_offset := seek(f, offset, .Current) or_return
defer seek(f, curr_offset, .Start)
o := win32.OVERLAPPED{
OffsetHigh = u32(offset>>32),
Offset = u32(offset),
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
}
h := win32.HANDLE(fd)
done: win32.DWORD
_ok_or_error(win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return
return int(done), nil
}
_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) {
return
}
_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) {
if offset < 0 {
return 0, .Invalid_Offset
}
b, offset := p, offset
for len(b) > 0 {
m := _pwrite(fd, b, offset) or_return
sync.guard(&f.impl.p_mutex)
p, offset := p, offset
for len(p) > 0 {
m := pwrite(f, p, offset) or_return
n += m
b = b[m:]
p = p[m:]
offset += i64(m)
}
return
}
_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) {
_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) {
// TODO(bill)
return
}
_file_size :: proc(fd: Handle) -> (n: i64, err: Error) {
_file_size :: proc(f: ^File) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER
err = _ok_or_error(win32.GetFileSizeEx(win32.HANDLE(fd), &length))
return i64(length), err
handle := _handle(f)
if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error()
}
n = i64(length)
return
}
_sync :: proc(fd: Handle) -> Error {
_sync :: proc(f: ^File) -> Error {
return _flush(f)
}
_flush :: proc(f: ^File) -> Error {
handle := _handle(f)
if !win32.FlushFileBuffers(handle) {
return _get_platform_error()
}
return nil
}
_flush :: proc(fd: Handle) -> Error {
return _ok_or_error(win32.FlushFileBuffers(win32.HANDLE(fd)))
}
_truncate :: proc(fd: Handle, size: i64) -> Error {
offset := seek(fd, size, .Start) or_return
defer seek(fd, offset, .Start)
return _ok_or_error(win32.SetEndOfFile(win32.HANDLE(fd)))
_truncate :: proc(f: ^File, size: i64) -> Error {
if f == nil {
return nil
}
curr_off := seek(f, 0, .Current) or_return
defer seek(f, curr_off, .Start)
seek(f, size, .Start) or_return
handle := _handle(f)
if !win32.SetEndOfFile(handle) {
return _get_platform_error()
}
return nil
}
_remove :: proc(name: string) -> Error {
p := win32.utf8_to_wstring(_fix_long_path(name))
err := _ok_or_error(win32.DeleteFileW(p))
p := _fix_long_path(name)
err, err1: Error
if !win32.DeleteFileW(p) {
err = _get_platform_error()
}
if err == nil {
return nil
}
err1 := _ok_or_error(win32.RemoveDirectoryW(p))
if !win32.RemoveDirectoryW(p) {
err1 = _get_platform_error()
}
if err1 == nil {
return nil
}
@@ -332,7 +464,10 @@ _remove :: proc(name: string) -> Error {
err = err1
} else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
err = _ok_or_error(win32.DeleteFileW(p))
err = nil
if !win32.DeleteFileW(p) {
err = _get_platform_error()
}
}
}
}
@@ -342,80 +477,253 @@ _remove :: proc(name: string) -> Error {
}
_rename :: proc(old_path, new_path: string) -> Error {
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
return _ok_or_error(win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING))
from := _fix_long_path(old_path)
to := _fix_long_path(new_path)
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil
}
return _get_platform_error()
}
_link :: proc(old_name, new_name: string) -> Error {
n := win32.utf8_to_wstring(_fix_long_path(new_name))
o := win32.utf8_to_wstring(_fix_long_path(old_name))
return _ok_or_error(win32.CreateHardLinkW(n, o, nil))
o := _fix_long_path(old_name)
n := _fix_long_path(new_name)
if win32.CreateHardLinkW(n, o, nil) {
return nil
}
return _get_platform_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..<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)
}
ws := p[4:]
switch {
case len(ws) >= 2 && ws[1] == ':':
return win32.utf16_to_utf8(ws, allocator)
case has_prefix(ws, `UNC\`):
ws[3] = '\\' // override data in buffer
return win32.utf16_to_utf8(ws[3:], allocator)
}
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, _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)
}
return win32.utf16_to_utf8(ws, allocator)
}
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)
}
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
}
_unlink :: proc(path: string) -> Error {
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
return _ok_or_error(win32.DeleteFileW(wpath))
}
_chdir :: proc(fd: Handle) -> Error {
_fchdir :: proc(f: ^File) -> Error {
if f == nil {
return nil
}
if !win32.SetCurrentDirectoryW(f.impl.wname) {
return _get_platform_error()
}
return nil
}
_chmod :: proc(fd: Handle, 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(fd: Handle, 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 := win32.utf8_to_wstring(path, context.temp_allocator)
return bool(win32.PathFileExistsW(wpath))
wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES
}
_is_file :: proc(fd: Handle) -> bool {
hnd := win32.HANDLE(fd)
file_info: win32.BY_HANDLE_FILE_INFORMATION
if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok {
return false
_is_file :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
}
no_flags :: win32.FILE_ATTRIBUTE_DIRECTORY | win32.FILE_ATTRIBUTE_DEVICE
yes_flags :: win32.FILE_ATTRIBUTE_NORMAL
return (file_info.dwFileAttributes & no_flags == 0) && (file_info.dwFileAttributes & yes_flags != 0)
return false
}
_is_dir :: proc(fd: Handle) -> bool {
hnd := win32.HANDLE(fd)
file_info: win32.BY_HANDLE_FILE_INFORMATION
if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok {
return false
_is_dir :: proc(path: string) -> bool {
wpath := _fix_long_path(path)
attribs := win32.GetFileAttributesW(wpath)
if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES {
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
}
return file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0
return false
}
-1
View File
@@ -102,7 +102,6 @@ MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE
@thread_local _local_region: ^Region
//_local_region: ^Region
global_regions: ^Region
+3 -1
View File
@@ -1,5 +1,7 @@
package os2
import "core:runtime"
Path_Separator :: _Path_Separator // OS-Specific
Path_List_Separator :: _Path_List_Separator // OS-Specific
@@ -21,7 +23,7 @@ remove_all :: proc(path: string) -> Error {
getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _getwd(allocator)
}
setwd :: proc(dir: string) -> (err: Error) {
+41 -23
View File
@@ -2,6 +2,8 @@
package os2
import "core:strings"
import "core:strconv"
import "core:runtime"
import "core:sys/unix"
_Path_Separator :: '/'
@@ -37,31 +39,31 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error {
}
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
_mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error {
_mkdirat :: proc(dfd: int, path: []u8, perm: int, has_created: ^bool) -> Error {
if len(path) == 0 {
return _ok_or_error(unix.sys_close(int(dfd)))
return _ok_or_error(unix.sys_close(dfd))
}
i: int
for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {}
path[i] = 0
new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS)
new_dfd := unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS)
switch new_dfd {
case -ENOENT:
if res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm); res < 0 {
if res := unix.sys_mkdirat(dfd, cstring(&path[0]), perm); res < 0 {
return _get_platform_error(res)
}
has_created^ = true
if new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 {
if new_dfd = unix.sys_openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 {
return _get_platform_error(new_dfd)
}
fallthrough
case 0:
if res := unix.sys_close(int(dfd)); res < 0 {
if res := unix.sys_close(dfd); res < 0 {
return _get_platform_error(res)
}
// skip consecutive '/'
for i += 1; i < len(path) && path[i] == '/'; i += 1 {}
return _mkdirat(Handle(new_dfd), path[i:], perm, has_created)
return _mkdirat(new_dfd, path[i:], perm, has_created)
case:
return _get_platform_error(new_dfd)
}
@@ -101,7 +103,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
}
has_created: bool
_mkdirat(Handle(dfd), path_bytes, int(perm & 0o777), &has_created) or_return
_mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return
if has_created {
return nil
}
@@ -120,13 +122,13 @@ dirent64 :: struct {
_remove_all :: proc(path: string) -> Error {
DT_DIR :: 4
_remove_all_dir :: proc(dfd: Handle) -> Error {
_remove_all_dir :: proc(dfd: int) -> Error {
n := 64
buf := make([]u8, n)
defer delete(buf)
loop: for {
getdents_res := unix.sys_getdents64(int(dfd), &buf[0], n)
getdents_res := unix.sys_getdents64(dfd, &buf[0], n)
switch getdents_res {
case -EINVAL:
delete(buf)
@@ -161,15 +163,15 @@ _remove_all :: proc(path: string) -> Error {
switch d.d_type {
case DT_DIR:
handle_i := unix.sys_openat(int(dfd), d_name_cstr, _OPENDIR_FLAGS)
if handle_i < 0 {
return _get_platform_error(handle_i)
new_dfd := unix.sys_openat(dfd, d_name_cstr, _OPENDIR_FLAGS)
if new_dfd < 0 {
return _get_platform_error(new_dfd)
}
defer unix.sys_close(handle_i)
_remove_all_dir(Handle(handle_i)) or_return
unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR))
defer unix.sys_close(new_dfd)
_remove_all_dir(new_dfd) or_return
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr, int(unix.AT_REMOVEDIR))
case:
unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr)
unlink_res = unix.sys_unlinkat(dfd, d_name_cstr)
}
if unlink_res < 0 {
@@ -185,21 +187,20 @@ _remove_all :: proc(path: string) -> Error {
delete(path_cstr)
}
handle_i := unix.sys_open(path_cstr, _OPENDIR_FLAGS)
switch handle_i {
fd := unix.sys_open(path_cstr, _OPENDIR_FLAGS)
switch fd {
case -ENOTDIR:
return _ok_or_error(unix.sys_unlink(path_cstr))
case -4096..<0:
return _get_platform_error(handle_i)
return _get_platform_error(fd)
}
fd := Handle(handle_i)
defer close(fd)
defer unix.sys_close(fd)
_remove_all_dir(fd) or_return
return _ok_or_error(unix.sys_rmdir(path_cstr))
}
_getwd :: proc(allocator := context.allocator) -> (string, Error) {
_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) {
// NOTE(tetra): I would use PATH_MAX here, but I was not able to find
// an authoritative value for it across all systems.
// The largest value I could find was 4096, so might as well use the page size.
@@ -227,3 +228,20 @@ _setwd :: proc(dir: string) -> Error {
}
return _ok_or_error(unix.sys_chdir(dir_cstr))
}
_get_full_path :: proc(fd: int, allocator := context.allocator) -> string {
PROC_FD_PATH :: "/proc/self/fd/"
buf: [32]u8
copy(buf[:], PROC_FD_PATH)
strconv.itoa(buf[len(PROC_FD_PATH):], fd)
fullpath: string
err: Error
if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
return ""
}
return fullpath
}
+133 -2
View File
@@ -1,6 +1,10 @@
//+private
package os2
import win32 "core:sys/windows"
import "core:runtime"
import "core:strings"
_Path_Separator :: '\\'
_Path_List_Separator :: ';'
@@ -9,11 +13,58 @@ _is_path_separator :: proc(c: byte) -> bool {
}
_mkdir :: proc(name: string, perm: File_Mode) -> Error {
if !win32.CreateDirectoryW(_fix_long_path(name), nil) {
return _get_platform_error()
}
return nil
}
_mkdir_all :: proc(path: string, perm: File_Mode) -> Error {
// TODO(bill): _mkdir_all for windows
fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) {
if len(p) == len(`\\?\c:`) {
if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' {
s = strings.concatenate_safe({p, `\`}, _file_allocator()) or_return
allocated = true
return
}
}
return p, false, nil
}
dir, err := stat(path, _temp_allocator())
if err == nil {
if dir.is_dir {
return nil
}
return .Exist
}
i := len(path)
for i > 0 && is_path_separator(path[i-1]) {
i -= 1
}
j := i
for j > 0 && !is_path_separator(path[j-1]) {
j -= 1
}
if j > 1 {
new_path, allocated := fix_root_directory(path[:j-1]) or_return
defer if allocated {
delete(new_path, _file_allocator())
}
mkdir_all(new_path, perm) or_return
}
err = mkdir(path, perm)
if err != nil {
dir1, err1 := lstat(path, _temp_allocator())
if err1 == nil && dir1.is_dir {
return nil
}
return err
}
return nil
}
@@ -22,10 +73,90 @@ _remove_all :: proc(path: string) -> Error {
return nil
}
_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) {
_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
// TODO(bill)
return "", nil
}
_setwd :: proc(dir: string) -> (err: Error) {
// TODO(bill)
return nil
}
can_use_long_paths: bool
@(init)
init_long_path_support :: proc() {
// TODO(bill): init_long_path_support
// ADD THIS SHIT
// registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`)
can_use_long_paths = false
}
_fix_long_path_slice :: proc(path: string) -> []u16 {
return win32.utf8_to_utf16(_fix_long_path_internal(path))
}
_fix_long_path :: proc(path: string) -> win32.wstring {
return win32.utf8_to_wstring(_fix_long_path_internal(path))
}
_fix_long_path_internal :: proc(path: string) -> string {
if can_use_long_paths {
return path
}
// When using win32 to create a directory, the path
// cannot be too long that you cannot append an 8.3
// file name, because MAX_PATH is 260, 260-12 = 248
if len(path) < 248 {
return path
}
// UNC paths do not need to be modified
if len(path) >= 2 && path[:2] == `\\` {
return path
}
if !_is_abs(path) { // relative path
return path
}
PREFIX :: `\\?`
path_buf := make([]byte, len(PREFIX)+len(path)+1, _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])):
// Skip \..\ paths
return path
case:
path_buf[w] = '\\'
w += 1
for r < n && !is_path_separator(path[r]) {
path_buf[w] = path[r]
r += 1
w += 1
}
}
}
// Root directories require a trailing \
if w == len(`\\?\c:`) {
path_buf[w] = '\\'
w += 1
}
return string(path_buf[:w])
}
+1 -1
View File
@@ -1,5 +1,5 @@
package os2
pipe :: proc() -> (r, w: Handle, err: Error) {
pipe :: proc() -> (r, w: ^File, err: Error) {
return _pipe()
}
+2 -2
View File
@@ -1,7 +1,7 @@
//+private
package os2
_pipe :: proc() -> (r, w: Handle, err: Error) {
return INVALID_HANDLE, INVALID_HANDLE, nil
_pipe :: proc() -> (r, w: ^File, err: Error) {
return nil, nil, nil
}
+4 -8
View File
@@ -3,15 +3,11 @@ package os2
import win32 "core:sys/windows"
_pipe :: proc() -> (r, w: Handle, err: Error) {
sa: win32.SECURITY_ATTRIBUTES
sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
sa.bInheritHandle = true
_pipe :: proc() -> (r, w: ^File, err: Error) {
p: [2]win32.HANDLE
if !win32.CreatePipe(&p[0], &p[1], &sa, 0) {
return 0, 0, Platform_Error{i32(win32.GetLastError())}
if !win32.CreatePipe(&p[0], &p[1], nil, 0) {
return nil, nil, _get_platform_error()
}
return Handle(p[0]), Handle(p[1]), nil
return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil
}
+1 -1
View File
@@ -46,7 +46,7 @@ Process :: struct {
Process_Attributes :: struct {
dir: string,
env: []string,
files: []Handle,
files: []^File,
sys: ^Process_Attributes_OS_Specific,
}
+7 -6
View File
@@ -1,6 +1,7 @@
package os2
import "core:time"
import "core:runtime"
File_Info :: struct {
fullpath: string,
@@ -13,26 +14,26 @@ File_Info :: struct {
access_time: time.Time,
}
file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) {
file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.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) {
file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
delete(fi.fullpath, allocator)
}
fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) {
return _fstat(fd, allocator)
fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
return _fstat(f, allocator)
}
stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _stat(name, allocator)
}
lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _lstat(name, allocator)
}
+13 -8
View File
@@ -2,6 +2,7 @@
package os2
import "core:time"
import "core:runtime"
import "core:sys/unix"
import "core:path/filepath"
@@ -59,7 +60,7 @@ Unix_File_Time :: struct {
}
@private
OS_Stat :: struct {
_Stat :: struct {
device_id: u64, // ID of device containing file
serial: u64, // File serial number
nlink: u64, // Number of hard links
@@ -82,16 +83,20 @@ OS_Stat :: struct {
}
_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) {
s: OS_Stat
result := unix.sys_fstat(int(fd), &s)
_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) {
return _fstat_internal(f.impl.fd, allocator)
}
_fstat_internal :: proc(fd: int, allocator: runtime.Allocator) -> (File_Info, Error) {
s: _Stat
result := unix.sys_fstat(fd, &s)
if result < 0 {
return {}, _get_platform_error(result)
}
// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
fi := File_Info {
fullpath = _name(fd, allocator),
fullpath = _get_full_path(fd, allocator),
name = "",
size = s.size,
mode = 0,
@@ -117,7 +122,7 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error
return {}, _get_platform_error(fd)
}
defer unix.sys_close(fd)
return _fstat(Handle(fd), allocator)
return _fstat_internal(fd, allocator)
}
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
@@ -130,14 +135,14 @@ _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Erro
return {}, _get_platform_error(fd)
}
defer unix.sys_close(fd)
return _fstat(Handle(fd), allocator)
return _fstat_internal(fd, allocator)
}
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
_stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) {
_stat_internal :: proc(name: string) -> (s: _Stat, res: int) {
name_cstr, allocated := _name_to_cstring(name)
defer if allocated {
delete(name_cstr)
+131 -127
View File
@@ -1,21 +1,22 @@
//+private
package os2
import "core:runtime"
import "core:time"
import "core:strings"
import win32 "core:sys/windows"
_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) {
if fd == 0 {
return {}, .Invalid_Argument
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if f == nil || f.impl.fd == nil {
return {}, nil
}
context.allocator = allocator
path, err := _cleanpath_from_handle(fd)
path, err := _cleanpath_from_handle(f, allocator)
if err != nil {
return {}, err
}
h := win32.HANDLE(fd)
h := _handle(f)
switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
fi: File_Info
@@ -25,58 +26,52 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error)
return fi, nil
}
return _file_info_from_get_file_information_by_handle(path, h)
return _file_info_from_get_file_information_by_handle(path, h, allocator)
}
_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS)
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
}
_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT)
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
}
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) {
context.allocator = allocator
full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
name := name
if name == "" {
name = "."
}
p := win32.utf8_to_utf16(name, context.temp_allocator)
buf := make([dynamic]u16, 100)
for {
n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 {
delete(buf)
return "", _get_platform_error()
}
if n <= u32(len(buf)) {
return win32.utf16_to_utf8(buf[:n]), nil
}
resize(&buf, len(buf)*2)
}
p := win32.utf8_to_utf16(name, _temp_allocator())
return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 {
return "", _get_platform_error()
}
buf := make([]u16, n+1, _temp_allocator())
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 {
return "", _get_platform_error()
}
return win32.utf16_to_utf8(buf[:n], allocator)
}
internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) {
internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
if len(name) == 0 {
return {}, .Not_Exist
}
context.allocator = allocator
wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator)
wname := _fix_long_path(name)
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)
return _file_info_from_win32_file_attribute_data(&fa, name, allocator)
}
err := 0 if ok else win32.GetLastError()
@@ -90,7 +85,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
}
win32.FindClose(sh)
return _file_info_from_win32_find_data(&fd, name)
return _file_info_from_win32_find_data(&fd, name, allocator)
}
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
@@ -99,7 +94,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co
return
}
defer win32.CloseHandle(h)
return _file_info_from_get_file_information_by_handle(name, h)
return _file_info_from_get_file_information_by_handle(name, h, allocator)
}
@@ -124,56 +119,40 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
}
_cleanpath_from_handle :: proc(fd: Handle) -> (string, Error) {
if fd == 0 {
return "", .Invalid_Argument
_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
if f == nil || f.impl.fd == nil {
return "", nil
}
h := win32.HANDLE(fd)
h := _handle(f)
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 "", Platform_Error{i32(err)}
case win32.ERROR_NOT_ENOUGH_MEMORY:
MAX_PATH = MAX_PATH*2 + 1
continue
}
break
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
if n == 0 {
return "", _get_platform_error()
}
return _cleanpath_from_buf(buf), nil
buf := make([]u16, max(n, 260)+1, _temp_allocator())
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_from_buf(buf[:n], allocator)
}
_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Error) {
if fd == 0 {
return nil, .Invalid_Argument
_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
if f == nil || f.impl.fd == nil {
return nil, nil
}
h := win32.HANDLE(fd)
h := _handle(f)
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, Platform_Error{i32(err)}
case win32.ERROR_NOT_ENOUGH_MEMORY:
MAX_PATH = MAX_PATH*2 + 1
continue
}
break
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
if n == 0 {
return nil, _get_platform_error()
}
return _cleanpath_strip_prefix(buf), nil
buf := make([]u16, max(n, 260)+1, _temp_allocator())
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil
}
_cleanpath_from_buf :: proc(buf: []u16) -> string {
_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
buf := buf
buf = _cleanpath_strip_prefix(buf)
return win32.utf16_to_utf8(buf, context.allocator)
return win32.utf16_to_utf8(buf, allocator)
}
@@ -215,15 +194,15 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
_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 {
_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode |= 0o444
} else {
mode |= 0o666
}
is_sym := false
if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
if file_attributes & 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
@@ -232,7 +211,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA
if is_sym {
mode |= File_Mode_Sym_Link
} else {
if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
mode |= 0o111 | File_Mode_Dir
}
@@ -245,7 +224,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA
}
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) {
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
@@ -255,14 +234,14 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE
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.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) {
_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
@@ -272,14 +251,14 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
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.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) {
_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(h, &d) {
return {}, _get_platform_error()
@@ -290,7 +269,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
err := win32.GetLastError()
if err != win32.ERROR_INVALID_PARAMETER {
return {}, Platform_Error{i32(err)}
return {}, Platform_Error(err)
}
// Indicate this is a symlink on FAT file systems
ti.ReparseTag = 0
@@ -312,58 +291,83 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
return fi, nil
}
_is_abs :: proc(path: string) -> bool {
if len(path) > 0 && path[0] == '/' {
return true
reserved_names := [?]string{
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
_is_reserved_name :: proc(path: string) -> bool {
if len(path) == 0 {
return false
}
if len(path) > 2 {
switch path[0] {
case 'A'..='Z', 'a'..='z':
return path[1] == ':' && is_path_separator(path[2])
for reserved in reserved_names {
if strings.equal_fold(path, reserved) {
return true
}
}
return false
}
_fix_long_path :: proc(path: string) -> string {
if len(path) < 248 {
return path
}
_is_UNC :: proc(path: string) -> bool {
return _volume_name_len(path) > 2
}
if len(path) >= 2 && path[:2] == `\\` {
return path
}
if !_is_abs(path) {
return path
}
_volume_name_len :: proc(path: string) -> int {
if ODIN_OS == .Windows {
if len(path) < 2 {
return 0
}
c := path[0]
if path[1] == ':' {
switch c {
case 'a'..='z', 'A'..='Z':
return 2
}
}
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
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
!_is_path_separator(path[2]) && path[2] != '.' {
for n := 3; n < l-1; n += 1 {
if _is_path_separator(path[n]) {
n += 1
if !_is_path_separator(path[n]) {
if path[n] == '.' {
break
}
}
for ; n < l; n += 1 {
if _is_path_separator(path[n]) {
break
}
}
return n
}
break
}
}
}
if w == len(`\\?\c:`) {
path_buf[w] = '\\'
w += 1
}
return string(path_buf[:w])
return 0
}
_is_abs :: proc(path: string) -> bool {
if _is_reserved_name(path) {
return true
}
l := _volume_name_len(path)
if l == 0 {
return false
}
path := path
path = path[l:]
if path == "" {
return false
}
return is_path_separator(path[0])
}
+5 -4
View File
@@ -1,14 +1,15 @@
package os2
import "core:runtime"
create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
create_temp :: proc(dir, pattern: string) -> (^File, Error) {
return _create_temp(dir, pattern)
}
mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
return _mkdir_temp(dir, pattern)
mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) {
return _mkdir_temp(dir, pattern, allocator)
}
temp_dir :: proc(allocator := context.allocator) -> string {
temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) {
return _temp_dir(allocator)
}
+7 -5
View File
@@ -1,18 +1,20 @@
//+private
package os2
import "core:runtime"
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
_create_temp :: proc(dir, pattern: string) -> (^File, Error) {
//TODO
return 0, nil
return nil, nil
}
_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) {
//TODO
return "", nil
}
_temp_dir :: proc(allocator := context.allocator) -> string {
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) {
//TODO
return ""
return "", nil
}
+17 -17
View File
@@ -1,29 +1,29 @@
//+private
package os2
import "core:runtime"
import win32 "core:sys/windows"
_create_temp :: proc(dir, pattern: string) -> (Handle, Error) {
return 0, nil
_create_temp :: proc(dir, pattern: string) -> (^File, Error) {
return nil, nil
}
_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) {
_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) {
return "", nil
}
_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)
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
n := win32.GetTempPathW(0, nil)
if n == 0 {
return "", nil
}
b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator())
n = win32.GetTempPathW(u32(len(b)), raw_data(b))
if n == 3 && b[1] == ':' && b[2] == '\\' {
} else if n > 0 && b[n-1] == '\\' {
n -= 1
}
return win32.utf16_to_utf8(b[:n], allocator)
}
+38 -29
View File
@@ -1,66 +1,75 @@
package os2
import "core:strings"
import "core:runtime"
user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
found: bool
#partial switch ODIN_OS {
case .Windows:
dir = get_env("LocalAppData") or_return
if dir != "" {
dir = strings.clone(dir, allocator)
dir, found = get_env("LocalAppData")
if found {
dir = strings.clone_safe(dir, allocator) or_return
}
case .Darwin:
dir = get_env("HOME") or_return
if dir != "" {
dir = strings.concatenate({dir, "/Library/Caches"}, allocator)
dir, found = get_env("HOME")
if found {
dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME") or_return
if dir == "" {
dir = get_env("HOME") or_return
if dir == "" {
dir, found = get_env("XDG_CACHE_HOME")
if found {
dir, found = get_env("HOME")
if !found {
return
}
dir = strings.concatenate({dir, "/.cache"}, allocator)
dir = strings.concatenate_safe({dir, "/.cache"}, allocator) or_return
}
}
is_defined = dir != ""
if !found || dir == "" {
err = .Invalid_Path
}
return
}
user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) {
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
found: bool
#partial switch ODIN_OS {
case .Windows:
dir = get_env("AppData") or_return
if dir != "" {
dir = strings.clone(dir, allocator)
dir, found = get_env("AppData")
if found {
dir = strings.clone_safe(dir, allocator) or_return
}
case .Darwin:
dir = get_env("HOME") or_return
if dir != "" {
dir = strings.concatenate({dir, "/Library/Application Support"}, allocator)
dir, found = get_env("HOME")
if found {
dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME") or_return
if dir == "" {
dir = get_env("HOME") or_return
if dir == "" {
dir, found = get_env("XDG_CACHE_HOME")
if !found {
dir, found = get_env("HOME")
if !found {
return
}
dir = strings.concatenate({dir, "/.config"}, allocator)
dir = strings.concatenate_safe({dir, "/.config"}, allocator) or_return
}
}
is_defined = dir != ""
if !found || dir == "" {
err = .Invalid_Path
}
return
}
user_home_dir :: proc() -> (dir: string, is_defined: bool) {
user_home_dir :: proc() -> (dir: string, err: Error) {
env := "HOME"
#partial switch ODIN_OS {
case .Windows:
env = "USERPROFILE"
}
v := get_env(env) or_return
return v, true
if v, found := get_env(env); found {
return v, nil
}
return "", .Invalid_Path
}
+12 -15
View File
@@ -163,9 +163,6 @@ O_SYNC :: 0x0080
O_ASYNC :: 0x0040
O_CLOEXEC :: 0x1000000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3
SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE
@@ -279,7 +276,7 @@ foreign libc {
@(link_name="__error") __error :: proc() -> ^int ---
@(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
@(link_name="close") _unix_close :: proc(handle: Handle) ---
@(link_name="close") _unix_close :: proc(handle: Handle) -> c.int ---
@(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
@(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int ---
@(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int ---
@@ -298,13 +295,13 @@ foreign libc {
@(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int ---
@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---
@(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int ---
@(link_name="__fcntl") _unix__fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int ---
@(link_name="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int ---
@(link_name="remove") _unix_remove :: proc(path: cstring) -> c.int ---
@(link_name="fchmod") _unix_fchmod :: proc(fildes: Handle, mode: u16) -> c.int ---
@(link_name="fchmod") _unix_fchmod :: proc(fd: Handle, mode: u16) -> c.int ---
@(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr ---
@(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr ---
@@ -364,12 +361,12 @@ when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 {
return handle, 0
}
fchmod :: proc(fildes: Handle, mode: u16) -> Errno {
return cast(Errno)_unix_fchmod(fildes, mode)
fchmod :: proc(fd: Handle, mode: u16) -> Errno {
return cast(Errno)_unix_fchmod(fd, mode)
}
close :: proc(fd: Handle) {
_unix_close(fd)
close :: proc(fd: Handle) -> bool {
return _unix_close(fd) == 0
}
write :: proc(fd: Handle, data: []u8) -> (int, Errno) {
@@ -480,12 +477,12 @@ is_dir :: proc {is_dir_path, is_dir_handle}
rename :: proc(old: string, new: string) -> bool {
old_cstr := strings.clone_to_cstring(old, context.temp_allocator)
new_cstr := strings.clone_to_cstring(new, context.temp_allocator)
return _unix_rename(old_cstr, new_cstr) != -1
return _unix_rename(old_cstr, new_cstr) != -1
}
remove :: proc(path: string) -> bool {
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
return _unix_remove(path_cstr) != -1
return _unix_remove(path_cstr) != -1
}
@private
@@ -549,7 +546,7 @@ _rewinddir :: proc(dirp: Dir) {
_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) {
result: ^Dirent
rc := _unix_readdir_r(dirp, &entry, &result)
if rc != 0 {
err = Errno(get_last_error())
return
@@ -589,7 +586,7 @@ _readlink :: proc(path: string) -> (string, Errno) {
absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) {
buf : [256]byte
res := _unix_fcntl(fd, F_GETPATH, &buf[0])
res := _unix__fcntl(fd, F_GETPATH, &buf[0])
if res != 0 {
return "", Errno(get_last_error())
}
-3
View File
@@ -123,9 +123,6 @@ O_ASYNC :: 0x02000
O_CLOEXEC :: 0x80000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3
SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE
+24 -6
View File
@@ -167,9 +167,6 @@ O_ASYNC :: 0x02000
O_CLOEXEC :: 0x80000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
SEEK_DATA :: 3
SEEK_HOLE :: 4
SEEK_MAX :: SEEK_HOLE
@@ -418,6 +415,7 @@ foreign libc {
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
@(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int ---
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr ---
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
@@ -582,6 +580,11 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
is_file :: proc {is_file_path, is_file_handle}
is_dir :: proc {is_dir_path, is_dir_handle}
exists :: proc(path: string) -> bool {
cpath := strings.clone_to_cstring(path, context.temp_allocator)
res := _unix_access(cpath, O_RDONLY)
return res == 0
}
// NOTE(bill): Uses startup to initialize it
@@ -767,13 +770,28 @@ heap_free :: proc(ptr: rawptr) {
_unix_free(ptr)
}
getenv :: proc(name: string) -> (string, bool) {
path_str := strings.clone_to_cstring(name, context.temp_allocator)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str)
if cstr == nil {
return "", false
}
return string(cstr), true
return strings.clone(string(cstr), allocator), true
}
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator)
return
}
set_env :: proc(key, value: string) -> Errno {
s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator)
res := _unix_putenv(strings.unsafe_string_to_cstring(s))
if res < 0 {
return Errno(get_last_error())
}
return ERROR_NONE
}
get_current_directory :: proc() -> string {
-4
View File
@@ -125,10 +125,6 @@ O_EXCL :: 0x00800
O_NOCTTY :: 0x08000
O_CLOEXEC :: 0x10000
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
RTLD_LAZY :: 0x001
RTLD_NOW :: 0x002
RTLD_LOCAL :: 0x000
-1
View File
@@ -119,7 +119,6 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e
}
stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
context.allocator = allocator
s: OS_Stat
+3 -3
View File
@@ -20,7 +20,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa
return "", Errno(win32.GetLastError())
}
if n <= u32(len(buf)) {
return win32.utf16_to_utf8(buf[:n], allocator), ERROR_NONE
return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE
}
resize(&buf, len(buf)*2)
}
@@ -136,7 +136,7 @@ cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) {
if err != 0 {
return "", err
}
return win32.utf16_to_utf8(buf, context.allocator), err
return win32.utf16_to_utf8(buf, context.allocator) or_else "", err
}
@(private)
cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
@@ -157,7 +157,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) {
cleanpath_from_buf :: proc(buf: []u16) -> string {
buf := buf
buf = cleanpath_strip_prefix(buf)
return win32.utf16_to_utf8(buf, context.allocator)
return win32.utf16_to_utf8(buf, context.allocator) or_else ""
}
@(private)