mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-25 14:31:36 +00:00
Deprecate core:os/old as advertised.
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
#+build darwin, linux, netbsd, freebsd, openbsd
|
||||
package os_old
|
||||
|
||||
import "core:strings"
|
||||
|
||||
@(require_results)
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
dupfd := _dup(fd) or_return
|
||||
dirp := _fdopendir(dupfd) or_return
|
||||
defer _closedir(dirp)
|
||||
|
||||
dirpath := absolute_path_from_handle(dupfd) or_return
|
||||
defer delete(dirpath)
|
||||
|
||||
n := n
|
||||
size := n
|
||||
if n <= 0 {
|
||||
n = -1
|
||||
size = 100
|
||||
}
|
||||
|
||||
dfi := make([dynamic]File_Info, 0, size, allocator) or_return
|
||||
defer if err != nil {
|
||||
for fi_ in dfi {
|
||||
file_info_delete(fi_, allocator)
|
||||
}
|
||||
delete(dfi)
|
||||
}
|
||||
|
||||
for {
|
||||
entry: Dirent
|
||||
end_of_stream: bool
|
||||
entry, err, end_of_stream = _readdir(dirp)
|
||||
if err != nil {
|
||||
return
|
||||
} else if end_of_stream {
|
||||
break
|
||||
}
|
||||
|
||||
fi_: File_Info
|
||||
filename := string(cstring(&entry.name[0]))
|
||||
|
||||
if filename == "." || filename == ".." {
|
||||
continue
|
||||
}
|
||||
|
||||
fullpath := strings.join({ dirpath, filename }, "/", allocator)
|
||||
|
||||
s: OS_Stat
|
||||
s, err = _lstat(fullpath)
|
||||
if err != nil {
|
||||
delete(fullpath, allocator)
|
||||
return
|
||||
}
|
||||
_fill_file_info_from_stat(&fi_, s)
|
||||
fi_.fullpath = fullpath
|
||||
fi_.name = path_base(fi_.fullpath)
|
||||
|
||||
append(&dfi, fi_)
|
||||
}
|
||||
|
||||
return dfi[:], nil
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "base:runtime"
|
||||
|
||||
@(require_results)
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
|
||||
@(require_results)
|
||||
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
|
||||
// Ignore "." and ".."
|
||||
if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
|
||||
return
|
||||
}
|
||||
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
||||
if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
|
||||
fi.mode |= 0o444
|
||||
} else {
|
||||
fi.mode |= 0o666
|
||||
}
|
||||
|
||||
is_sym := false
|
||||
if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 {
|
||||
is_sym = false
|
||||
} else {
|
||||
is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT
|
||||
}
|
||||
|
||||
if is_sym {
|
||||
fi.mode |= File_Mode_Sym_Link
|
||||
} else {
|
||||
if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
fi.mode |= 0o111 | File_Mode_Dir
|
||||
}
|
||||
|
||||
// fi.mode |= file_type_mode(h);
|
||||
}
|
||||
|
||||
windows_set_file_info_times(&fi, d)
|
||||
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0
|
||||
return
|
||||
}
|
||||
|
||||
if fd == 0 {
|
||||
return nil, ERROR_INVALID_HANDLE
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
h := win32.HANDLE(fd)
|
||||
|
||||
dir_fi, _ := file_info_from_get_file_information_by_handle("", h)
|
||||
if !dir_fi.is_dir {
|
||||
return nil, .Not_Dir
|
||||
}
|
||||
|
||||
n := n
|
||||
size := n
|
||||
if n <= 0 {
|
||||
n = -1
|
||||
size = 100
|
||||
}
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
|
||||
if len(wpath) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dfi := make([dynamic]File_Info, 0, size) or_return
|
||||
|
||||
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return
|
||||
copy(wpath_search, wpath)
|
||||
wpath_search[len(wpath)+0] = '\\'
|
||||
wpath_search[len(wpath)+1] = '*'
|
||||
wpath_search[len(wpath)+2] = 0
|
||||
|
||||
path := cleanpath_from_buf(wpath)
|
||||
defer delete(path)
|
||||
|
||||
find_data := &win32.WIN32_FIND_DATAW{}
|
||||
find_handle := win32.FindFirstFileW(cstring16(raw_data(wpath_search)), find_data)
|
||||
if find_handle == win32.INVALID_HANDLE_VALUE {
|
||||
err = get_last_error()
|
||||
return dfi[:], err
|
||||
}
|
||||
defer win32.FindClose(find_handle)
|
||||
for n != 0 {
|
||||
fi: File_Info
|
||||
fi = find_data_to_file_info(path, find_data)
|
||||
if fi.name != "" {
|
||||
append(&dfi, fi)
|
||||
n -= 1
|
||||
}
|
||||
|
||||
if !win32.FindNextFileW(find_handle, find_data) {
|
||||
e := get_last_error()
|
||||
if e == ERROR_NO_MORE_FILES {
|
||||
break
|
||||
}
|
||||
return dfi[:], e
|
||||
}
|
||||
}
|
||||
|
||||
return dfi[:], nil
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// The original implementation of `core:os`, to be removed in Q2 2026.
|
||||
package os_old
|
||||
@@ -1,140 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "base:runtime"
|
||||
|
||||
// lookup_env gets the value of the environment variable named by the key
|
||||
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
||||
// Otherwise the returned value will be empty and the boolean will be false
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
wkey := win32.utf8_to_wstring(key)
|
||||
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
|
||||
if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
|
||||
return "", false
|
||||
}
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
b, _ := make([dynamic]u16, n, context.temp_allocator)
|
||||
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
|
||||
if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
|
||||
return "", false
|
||||
}
|
||||
value, _ = win32.utf16_to_utf8(b[:n], allocator)
|
||||
found = true
|
||||
return
|
||||
}
|
||||
|
||||
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
|
||||
// Note that it is limited to environment names and values of 512 utf-16 values each
|
||||
// due to the necessary utf-8 <> utf-16 conversion.
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
key_buf: [513]u16
|
||||
wkey := win32.utf8_to_wstring(key_buf[:], key)
|
||||
if wkey == nil {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
|
||||
val_buf: [513]u16
|
||||
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
|
||||
if n2 == 0 {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else if int(n2) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
value = win32.utf16_to_utf8(buf, val_buf[:n2])
|
||||
|
||||
return value, nil
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
// get_env retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
|
||||
// set_env sets the value of the environment variable named by the key
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
k := win32.utf8_to_wstring(key)
|
||||
v := win32.utf8_to_wstring(value)
|
||||
|
||||
if !win32.SetEnvironmentVariableW(k, v) {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unset_env unsets a single environment variable
|
||||
unset_env :: proc(key: string) -> Error {
|
||||
k := win32.utf8_to_wstring(key)
|
||||
if !win32.SetEnvironmentVariableW(k, nil) {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// environ returns a copy of strings representing the environment, in the form "key=value"
|
||||
// NOTE: the slice of strings and the strings with be allocated using the supplied allocator
|
||||
@(require_results)
|
||||
environ :: proc(allocator := context.allocator) -> []string {
|
||||
envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW())
|
||||
if envs == nil {
|
||||
return nil
|
||||
}
|
||||
defer win32.FreeEnvironmentStringsW(envs)
|
||||
|
||||
r, err := make([dynamic]string, 0, 50, allocator)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for from, i := 0, 0; true; i += 1 {
|
||||
if c := envs[i]; c == 0 {
|
||||
if i <= from {
|
||||
break
|
||||
}
|
||||
append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "")
|
||||
from = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return r[:]
|
||||
}
|
||||
|
||||
|
||||
// clear_env deletes all environment variables
|
||||
clear_env :: proc() {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
envs := environ(context.temp_allocator)
|
||||
for env in envs {
|
||||
for j in 1..<len(env) {
|
||||
if env[j] == '=' {
|
||||
unset_env(env[0:j])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
|
||||
Platform_Error :: _Platform_Error
|
||||
#assert(size_of(Platform_Error) <= 4)
|
||||
#assert(intrinsics.type_has_nil(Platform_Error))
|
||||
|
||||
General_Error :: enum u32 {
|
||||
None,
|
||||
|
||||
Exist,
|
||||
Not_Exist,
|
||||
|
||||
Timeout,
|
||||
|
||||
Broken_Pipe,
|
||||
|
||||
Invalid_File,
|
||||
Invalid_Dir,
|
||||
Invalid_Path,
|
||||
Invalid_Callback,
|
||||
|
||||
Pattern_Has_Separator,
|
||||
|
||||
File_Is_Pipe,
|
||||
Not_Dir,
|
||||
|
||||
// Environment variable not found.
|
||||
Env_Var_Not_Found,
|
||||
}
|
||||
|
||||
|
||||
Errno :: Error // alias for legacy use
|
||||
|
||||
Error :: union #shared_nil {
|
||||
General_Error,
|
||||
io.Error,
|
||||
runtime.Allocator_Error,
|
||||
Platform_Error,
|
||||
}
|
||||
#assert(size_of(Error) == 8)
|
||||
|
||||
ERROR_NONE :: Error{}
|
||||
|
||||
ERROR_EOF :: io.Error.EOF
|
||||
|
||||
@(require_results)
|
||||
is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
|
||||
v := ferr.(Platform_Error) or_else {}
|
||||
return i32(v), i32(v) != 0
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
error_string :: proc "contextless" (ferr: Error) -> string {
|
||||
if ferr == nil {
|
||||
return ""
|
||||
}
|
||||
switch e in ferr {
|
||||
case General_Error:
|
||||
switch e {
|
||||
case .None: return ""
|
||||
case .Exist: return "file already exists"
|
||||
case .Not_Exist: return "file does not exist"
|
||||
case .Timeout: return "i/o timeout"
|
||||
case .Broken_Pipe: return "Broken pipe"
|
||||
case .Invalid_File: return "invalid file"
|
||||
case .Invalid_Dir: return "invalid directory"
|
||||
case .Invalid_Path: return "invalid path"
|
||||
case .Invalid_Callback: return "invalid callback"
|
||||
case .Pattern_Has_Separator: return "pattern has separator"
|
||||
case .File_Is_Pipe: return "file is pipe"
|
||||
case .Not_Dir: return "file is not directory"
|
||||
case .Env_Var_Not_Found: return "environment variable not found"
|
||||
}
|
||||
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 .Permission_Denied: return "permission denied"
|
||||
case .Closed: return "file already closed"
|
||||
case .No_Size: return "file has no definite size"
|
||||
case .Unsupported: return "unsupported"
|
||||
case .Unknown: //
|
||||
}
|
||||
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(e)
|
||||
}
|
||||
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
|
||||
err_str := error_string(ferr)
|
||||
|
||||
// msg + ": " + err_str + '\n'
|
||||
length := len(msg) + 2 + len(err_str) + 1
|
||||
buf_ := intrinsics.alloca(length, 1)
|
||||
buf := buf_[:length]
|
||||
|
||||
copy(buf, msg)
|
||||
buf[len(msg)] = ':'
|
||||
buf[len(msg) + 1] = ' '
|
||||
copy(buf[len(msg) + 2:], err_str)
|
||||
buf[length - 1] = '\n'
|
||||
return write(f, buf)
|
||||
}
|
||||
|
||||
|
||||
@(require_results, private)
|
||||
_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
when ODIN_OS == .Darwin {
|
||||
if s := string(_darwin_string_error(i32(e))); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
when ODIN_OS != .Linux {
|
||||
@(require_results)
|
||||
binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check {
|
||||
n := len(array)
|
||||
left, right := 0, n
|
||||
for left < right {
|
||||
mid := int(uint(left+right) >> 1)
|
||||
if array[mid] < key {
|
||||
left = mid+1
|
||||
} else {
|
||||
// equal or greater
|
||||
right = mid
|
||||
}
|
||||
}
|
||||
return left, left < n && array[left] == key
|
||||
}
|
||||
|
||||
err := runtime.Type_Info_Enum_Value(e)
|
||||
|
||||
ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum)
|
||||
if idx, ok := binary_search(ti.values, err); ok {
|
||||
return ti.names[idx]
|
||||
}
|
||||
} else {
|
||||
@(rodata, static)
|
||||
pe_strings := [Platform_Error]string{
|
||||
.NONE = "",
|
||||
.EPERM = "Operation not permitted",
|
||||
.ENOENT = "No such file or directory",
|
||||
.ESRCH = "No such process",
|
||||
.EINTR = "Interrupted system call",
|
||||
.EIO = "Input/output error",
|
||||
.ENXIO = "No such device or address",
|
||||
.E2BIG = "Argument list too long",
|
||||
.ENOEXEC = "Exec format error",
|
||||
.EBADF = "Bad file descriptor",
|
||||
.ECHILD = "No child processes",
|
||||
.EAGAIN = "Resource temporarily unavailable",
|
||||
.ENOMEM = "Cannot allocate memory",
|
||||
.EACCES = "Permission denied",
|
||||
.EFAULT = "Bad address",
|
||||
.ENOTBLK = "Block device required",
|
||||
.EBUSY = "Device or resource busy",
|
||||
.EEXIST = "File exists",
|
||||
.EXDEV = "Invalid cross-device link",
|
||||
.ENODEV = "No such device",
|
||||
.ENOTDIR = "Not a directory",
|
||||
.EISDIR = "Is a directory",
|
||||
.EINVAL = "Invalid argument",
|
||||
.ENFILE = "Too many open files in system",
|
||||
.EMFILE = "Too many open files",
|
||||
.ENOTTY = "Inappropriate ioctl for device",
|
||||
.ETXTBSY = "Text file busy",
|
||||
.EFBIG = "File too large",
|
||||
.ENOSPC = "No space left on device",
|
||||
.ESPIPE = "Illegal seek",
|
||||
.EROFS = "Read-only file system",
|
||||
.EMLINK = "Too many links",
|
||||
.EPIPE = "Broken pipe",
|
||||
.EDOM = "Numerical argument out of domain",
|
||||
.ERANGE = "Numerical result out of range",
|
||||
.EDEADLK = "Resource deadlock avoided",
|
||||
.ENAMETOOLONG = "File name too long",
|
||||
.ENOLCK = "No locks available",
|
||||
.ENOSYS = "Function not implemented",
|
||||
.ENOTEMPTY = "Directory not empty",
|
||||
.ELOOP = "Too many levels of symbolic links",
|
||||
.EUNKNOWN_41 = "Unknown Error (41)",
|
||||
.ENOMSG = "No message of desired type",
|
||||
.EIDRM = "Identifier removed",
|
||||
.ECHRNG = "Channel number out of range",
|
||||
.EL2NSYNC = "Level 2 not synchronized",
|
||||
.EL3HLT = "Level 3 halted",
|
||||
.EL3RST = "Level 3 reset",
|
||||
.ELNRNG = "Link number out of range",
|
||||
.EUNATCH = "Protocol driver not attached",
|
||||
.ENOCSI = "No CSI structure available",
|
||||
.EL2HLT = "Level 2 halted",
|
||||
.EBADE = "Invalid exchange",
|
||||
.EBADR = "Invalid request descriptor",
|
||||
.EXFULL = "Exchange full",
|
||||
.ENOANO = "No anode",
|
||||
.EBADRQC = "Invalid request code",
|
||||
.EBADSLT = "Invalid slot",
|
||||
.EUNKNOWN_58 = "Unknown Error (58)",
|
||||
.EBFONT = "Bad font file format",
|
||||
.ENOSTR = "Device not a stream",
|
||||
.ENODATA = "No data available",
|
||||
.ETIME = "Timer expired",
|
||||
.ENOSR = "Out of streams resources",
|
||||
.ENONET = "Machine is not on the network",
|
||||
.ENOPKG = "Package not installed",
|
||||
.EREMOTE = "Object is remote",
|
||||
.ENOLINK = "Link has been severed",
|
||||
.EADV = "Advertise error",
|
||||
.ESRMNT = "Srmount error",
|
||||
.ECOMM = "Communication error on send",
|
||||
.EPROTO = "Protocol error",
|
||||
.EMULTIHOP = "Multihop attempted",
|
||||
.EDOTDOT = "RFS specific error",
|
||||
.EBADMSG = "Bad message",
|
||||
.EOVERFLOW = "Value too large for defined data type",
|
||||
.ENOTUNIQ = "Name not unique on network",
|
||||
.EBADFD = "File descriptor in bad state",
|
||||
.EREMCHG = "Remote address changed",
|
||||
.ELIBACC = "Can not access a needed shared library",
|
||||
.ELIBBAD = "Accessing a corrupted shared library",
|
||||
.ELIBSCN = ".lib section in a.out corrupted",
|
||||
.ELIBMAX = "Attempting to link in too many shared libraries",
|
||||
.ELIBEXEC = "Cannot exec a shared library directly",
|
||||
.EILSEQ = "Invalid or incomplete multibyte or wide character",
|
||||
.ERESTART = "Interrupted system call should be restarted",
|
||||
.ESTRPIPE = "Streams pipe error",
|
||||
.EUSERS = "Too many users",
|
||||
.ENOTSOCK = "Socket operation on non-socket",
|
||||
.EDESTADDRREQ = "Destination address required",
|
||||
.EMSGSIZE = "Message too long",
|
||||
.EPROTOTYPE = "Protocol wrong type for socket",
|
||||
.ENOPROTOOPT = "Protocol not available",
|
||||
.EPROTONOSUPPORT = "Protocol not supported",
|
||||
.ESOCKTNOSUPPORT = "Socket type not supported",
|
||||
.EOPNOTSUPP = "Operation not supported",
|
||||
.EPFNOSUPPORT = "Protocol family not supported",
|
||||
.EAFNOSUPPORT = "Address family not supported by protocol",
|
||||
.EADDRINUSE = "Address already in use",
|
||||
.EADDRNOTAVAIL = "Cannot assign requested address",
|
||||
.ENETDOWN = "Network is down",
|
||||
.ENETUNREACH = "Network is unreachable",
|
||||
.ENETRESET = "Network dropped connection on reset",
|
||||
.ECONNABORTED = "Software caused connection abort",
|
||||
.ECONNRESET = "Connection reset by peer",
|
||||
.ENOBUFS = "No buffer space available",
|
||||
.EISCONN = "Transport endpoint is already connected",
|
||||
.ENOTCONN = "Transport endpoint is not connected",
|
||||
.ESHUTDOWN = "Cannot send after transport endpoint shutdown",
|
||||
.ETOOMANYREFS = "Too many references: cannot splice",
|
||||
.ETIMEDOUT = "Connection timed out",
|
||||
.ECONNREFUSED = "Connection refused",
|
||||
.EHOSTDOWN = "Host is down",
|
||||
.EHOSTUNREACH = "No route to host",
|
||||
.EALREADY = "Operation already in progress",
|
||||
.EINPROGRESS = "Operation now in progress",
|
||||
.ESTALE = "Stale file handle",
|
||||
.EUCLEAN = "Structure needs cleaning",
|
||||
.ENOTNAM = "Not a XENIX named type file",
|
||||
.ENAVAIL = "No XENIX semaphores available",
|
||||
.EISNAM = "Is a named type file",
|
||||
.EREMOTEIO = "Remote I/O error",
|
||||
.EDQUOT = "Disk quota exceeded",
|
||||
.ENOMEDIUM = "No medium found",
|
||||
.EMEDIUMTYPE = "Wrong medium type",
|
||||
.ECANCELED = "Operation canceled",
|
||||
.ENOKEY = "Required key not available",
|
||||
.EKEYEXPIRED = "Key has expired",
|
||||
.EKEYREVOKED = "Key has been revoked",
|
||||
.EKEYREJECTED = "Key was rejected by service",
|
||||
.EOWNERDEAD = "Owner died",
|
||||
.ENOTRECOVERABLE = "State not recoverable",
|
||||
.ERFKILL = "Operation not possible due to RF-kill",
|
||||
.EHWPOISON = "Memory page has hardware error",
|
||||
}
|
||||
if Platform_Error.NONE <= e && e <= max(Platform_Error) {
|
||||
return pe_strings[e]
|
||||
}
|
||||
}
|
||||
return "<unknown platform error>"
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
error_to_io_error :: proc(ferr: Error) -> io.Error {
|
||||
if ferr == nil {
|
||||
return .None
|
||||
}
|
||||
return ferr.(io.Error) or_else .Unknown
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
// Cross-platform `OS` interactions like file `I/O`.
|
||||
package os_old
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:io"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
|
||||
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, Error) {
|
||||
return write(fd, transmute([]byte)str)
|
||||
}
|
||||
|
||||
write_byte :: proc(fd: Handle, b: byte) -> (int, Error) {
|
||||
return write(fd, []byte{b})
|
||||
}
|
||||
|
||||
write_rune :: proc(fd: Handle, r: rune) -> (int, Error) {
|
||||
if r < utf8.RUNE_SELF {
|
||||
return write_byte(fd, byte(r))
|
||||
}
|
||||
|
||||
b, n := utf8.encode_rune(r)
|
||||
return write(fd, b[:n])
|
||||
}
|
||||
|
||||
write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
|
||||
wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool {
|
||||
n^ += m
|
||||
if merr != nil {
|
||||
err^ = merr
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if wrap(write_byte(f, '\''), &n, &err) { return }
|
||||
|
||||
switch r {
|
||||
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(f, "\\x"), &n, &err) { return }
|
||||
b: [2]byte
|
||||
s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
|
||||
switch len(s) {
|
||||
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(f, r), &n, &err) { return }
|
||||
}
|
||||
}
|
||||
_ = wrap(write_byte(f, '\''), &n, &err)
|
||||
return
|
||||
}
|
||||
|
||||
read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) {
|
||||
if len(buf) < min {
|
||||
return 0, io.Error.Short_Buffer
|
||||
}
|
||||
nn := max(int)
|
||||
for nn > 0 && n < min && err == nil {
|
||||
nn, err = read(fd, buf[n:])
|
||||
n += nn
|
||||
}
|
||||
if n >= min {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) {
|
||||
return read_at_least(fd, buf, len(buf))
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
file_size_from_path :: proc(path: string) -> i64 {
|
||||
fd, err := open(path, O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
defer close(fd)
|
||||
|
||||
length: i64
|
||||
if length, err = file_size(fd); err != nil {
|
||||
return -1
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
|
||||
err: Error
|
||||
data, err = read_entire_file_from_filename_or_err(name, allocator, loc)
|
||||
success = err == nil
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
|
||||
err: Error
|
||||
data, err = read_entire_file_from_handle_or_err(fd, allocator, loc)
|
||||
success = err == nil
|
||||
return
|
||||
}
|
||||
|
||||
read_entire_file :: proc {
|
||||
read_entire_file_from_filename,
|
||||
read_entire_file_from_handle,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
fd := open(name, O_RDONLY, 0) or_return
|
||||
defer close(fd)
|
||||
|
||||
return read_entire_file_from_handle_or_err(fd, allocator, loc)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
length := file_size(fd) or_return
|
||||
if length <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data = make([]byte, int(length), allocator, loc) or_return
|
||||
if data == nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer if err != nil {
|
||||
delete(data, allocator)
|
||||
}
|
||||
|
||||
bytes_read := read_full(fd, data) or_return
|
||||
data = data[:bytes_read]
|
||||
return
|
||||
}
|
||||
|
||||
read_entire_file_or_err :: proc {
|
||||
read_entire_file_from_filename_or_err,
|
||||
read_entire_file_from_handle_or_err,
|
||||
}
|
||||
|
||||
|
||||
write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) {
|
||||
return write_entire_file_or_err(name, data, truncate) == nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error {
|
||||
flags: int = O_WRONLY|O_CREATE
|
||||
if truncate {
|
||||
flags |= O_TRUNC
|
||||
}
|
||||
|
||||
mode: int = 0
|
||||
when OS == .Linux || OS == .Darwin {
|
||||
// NOTE(justasd): 644 (owner read, write; group read; others read)
|
||||
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
|
||||
}
|
||||
|
||||
fd := open(name, flags, mode) or_return
|
||||
defer close(fd)
|
||||
|
||||
for n := 0; n < len(data); {
|
||||
n += write(fd, data[n:]) or_return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
|
||||
return write(fd, ([^]byte)(data)[:len])
|
||||
}
|
||||
|
||||
read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) {
|
||||
return read(fd, ([^]byte)(data)[:len])
|
||||
}
|
||||
|
||||
heap_allocator_proc :: runtime.heap_allocator_proc
|
||||
heap_allocator :: runtime.heap_allocator
|
||||
|
||||
heap_alloc :: runtime.heap_alloc
|
||||
heap_resize :: runtime.heap_resize
|
||||
heap_free :: runtime.heap_free
|
||||
|
||||
@(require_results)
|
||||
processor_core_count :: proc() -> int {
|
||||
return _processor_core_count()
|
||||
}
|
||||
|
||||
// Always allocates for consistency.
|
||||
replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) {
|
||||
path := path
|
||||
|
||||
sb: strings.Builder
|
||||
strings.builder_init_none(&sb, allocator)
|
||||
for len(path) > 0 {
|
||||
switch path[0] {
|
||||
case '%': // Windows
|
||||
when ODIN_OS == .Windows {
|
||||
for r, i in path[1:] {
|
||||
if r == '%' {
|
||||
env_key := path[1:i+1]
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[i+1:] // % is part of key, so skip 1 character extra
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case '$': // Posix
|
||||
when ODIN_OS != .Windows {
|
||||
env_key := ""
|
||||
dollar_loop: for r, i in path[1:] {
|
||||
switch r {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
|
||||
case:
|
||||
env_key = path[1:i+1]
|
||||
break dollar_loop
|
||||
}
|
||||
}
|
||||
if len(env_key) > 0 {
|
||||
env_val := get_env(env_key, context.temp_allocator)
|
||||
strings.write_string(&sb, env_val)
|
||||
path = path[len(env_key):]
|
||||
}
|
||||
|
||||
} else {
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
case:
|
||||
strings.write_rune(&sb, rune(path[0]))
|
||||
}
|
||||
|
||||
path = path[1:]
|
||||
}
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,982 +0,0 @@
|
||||
package os_old
|
||||
|
||||
foreign import dl "system:dl"
|
||||
foreign import libc "system:c"
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:c"
|
||||
import "core:sys/freebsd"
|
||||
|
||||
Handle :: distinct i32
|
||||
File_Time :: distinct u64
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
_Platform_Error :: enum i32 {
|
||||
NONE = 0,
|
||||
EPERM = 1,
|
||||
ENOENT = 2,
|
||||
ESRCH = 3,
|
||||
EINTR = 4,
|
||||
EIO = 5,
|
||||
ENXIO = 6,
|
||||
E2BIG = 7,
|
||||
ENOEXEC = 8,
|
||||
EBADF = 9,
|
||||
ECHILD = 10,
|
||||
EBEADLK = 11,
|
||||
ENOMEM = 12,
|
||||
EACCESS = 13,
|
||||
EFAULT = 14,
|
||||
ENOTBLK = 15,
|
||||
EBUSY = 16,
|
||||
EEXIST = 17,
|
||||
EXDEV = 18,
|
||||
ENODEV = 19,
|
||||
ENOTDIR = 20,
|
||||
EISDIR = 21,
|
||||
EINVAL = 22,
|
||||
ENFILE = 23,
|
||||
EMFILE = 24,
|
||||
ENOTTY = 25,
|
||||
ETXTBSY = 26,
|
||||
EFBIG = 27,
|
||||
ENOSPC = 28,
|
||||
ESPIPE = 29,
|
||||
EROFS = 30,
|
||||
EMLINK = 31,
|
||||
EPIPE = 32,
|
||||
EDOM = 33,
|
||||
ERANGE = 34, /* Result too large */
|
||||
EAGAIN = 35,
|
||||
EINPROGRESS = 36,
|
||||
EALREADY = 37,
|
||||
ENOTSOCK = 38,
|
||||
EDESTADDRREQ = 39,
|
||||
EMSGSIZE = 40,
|
||||
EPROTOTYPE = 41,
|
||||
ENOPROTOOPT = 42,
|
||||
EPROTONOSUPPORT = 43,
|
||||
ESOCKTNOSUPPORT = 44,
|
||||
EOPNOTSUPP = 45,
|
||||
EPFNOSUPPORT = 46,
|
||||
EAFNOSUPPORT = 47,
|
||||
EADDRINUSE = 48,
|
||||
EADDRNOTAVAIL = 49,
|
||||
ENETDOWN = 50,
|
||||
ENETUNREACH = 51,
|
||||
ENETRESET = 52,
|
||||
ECONNABORTED = 53,
|
||||
ECONNRESET = 54,
|
||||
ENOBUFS = 55,
|
||||
EISCONN = 56,
|
||||
ENOTCONN = 57,
|
||||
ESHUTDOWN = 58,
|
||||
ETIMEDOUT = 60,
|
||||
ECONNREFUSED = 61,
|
||||
ELOOP = 62,
|
||||
ENAMETOOLING = 63,
|
||||
EHOSTDOWN = 64,
|
||||
EHOSTUNREACH = 65,
|
||||
ENOTEMPTY = 66,
|
||||
EPROCLIM = 67,
|
||||
EUSERS = 68,
|
||||
EDQUOT = 69,
|
||||
ESTALE = 70,
|
||||
EBADRPC = 72,
|
||||
ERPCMISMATCH = 73,
|
||||
EPROGUNAVAIL = 74,
|
||||
EPROGMISMATCH = 75,
|
||||
EPROCUNAVAIL = 76,
|
||||
ENOLCK = 77,
|
||||
ENOSYS = 78,
|
||||
EFTYPE = 79,
|
||||
EAUTH = 80,
|
||||
ENEEDAUTH = 81,
|
||||
EIDRM = 82,
|
||||
ENOMSG = 83,
|
||||
EOVERFLOW = 84,
|
||||
ECANCELED = 85,
|
||||
EILSEQ = 86,
|
||||
ENOATTR = 87,
|
||||
EDOOFUS = 88,
|
||||
EBADMSG = 89,
|
||||
EMULTIHOP = 90,
|
||||
ENOLINK = 91,
|
||||
EPROTO = 92,
|
||||
ENOTCAPABLE = 93,
|
||||
ECAPMODE = 94,
|
||||
ENOTRECOVERABLE = 95,
|
||||
EOWNERDEAD = 96,
|
||||
}
|
||||
EPERM :: Platform_Error.EPERM
|
||||
ENOENT :: Platform_Error.ENOENT
|
||||
ESRCH :: Platform_Error.ESRCH
|
||||
EINTR :: Platform_Error.EINTR
|
||||
EIO :: Platform_Error.EIO
|
||||
ENXIO :: Platform_Error.ENXIO
|
||||
E2BIG :: Platform_Error.E2BIG
|
||||
ENOEXEC :: Platform_Error.ENOEXEC
|
||||
EBADF :: Platform_Error.EBADF
|
||||
ECHILD :: Platform_Error.ECHILD
|
||||
EBEADLK :: Platform_Error.EBEADLK
|
||||
ENOMEM :: Platform_Error.ENOMEM
|
||||
EACCESS :: Platform_Error.EACCESS
|
||||
EFAULT :: Platform_Error.EFAULT
|
||||
ENOTBLK :: Platform_Error.ENOTBLK
|
||||
EBUSY :: Platform_Error.EBUSY
|
||||
EEXIST :: Platform_Error.EEXIST
|
||||
EXDEV :: Platform_Error.EXDEV
|
||||
ENODEV :: Platform_Error.ENODEV
|
||||
ENOTDIR :: Platform_Error.ENOTDIR
|
||||
EISDIR :: Platform_Error.EISDIR
|
||||
EINVAL :: Platform_Error.EINVAL
|
||||
ENFILE :: Platform_Error.ENFILE
|
||||
EMFILE :: Platform_Error.EMFILE
|
||||
ENOTTY :: Platform_Error.ENOTTY
|
||||
ETXTBSY :: Platform_Error.ETXTBSY
|
||||
EFBIG :: Platform_Error.EFBIG
|
||||
ENOSPC :: Platform_Error.ENOSPC
|
||||
ESPIPE :: Platform_Error.ESPIPE
|
||||
EROFS :: Platform_Error.EROFS
|
||||
EMLINK :: Platform_Error.EMLINK
|
||||
EPIPE :: Platform_Error.EPIPE
|
||||
EDOM :: Platform_Error.EDOM
|
||||
ERANGE :: Platform_Error.ERANGE
|
||||
EAGAIN :: Platform_Error.EAGAIN
|
||||
EINPROGRESS :: Platform_Error.EINPROGRESS
|
||||
EALREADY :: Platform_Error.EALREADY
|
||||
ENOTSOCK :: Platform_Error.ENOTSOCK
|
||||
EDESTADDRREQ :: Platform_Error.EDESTADDRREQ
|
||||
EMSGSIZE :: Platform_Error.EMSGSIZE
|
||||
EPROTOTYPE :: Platform_Error.EPROTOTYPE
|
||||
ENOPROTOOPT :: Platform_Error.ENOPROTOOPT
|
||||
EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
|
||||
ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
|
||||
EOPNOTSUPP :: Platform_Error.EOPNOTSUPP
|
||||
EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT
|
||||
EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT
|
||||
EADDRINUSE :: Platform_Error.EADDRINUSE
|
||||
EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL
|
||||
ENETDOWN :: Platform_Error.ENETDOWN
|
||||
ENETUNREACH :: Platform_Error.ENETUNREACH
|
||||
ENETRESET :: Platform_Error.ENETRESET
|
||||
ECONNABORTED :: Platform_Error.ECONNABORTED
|
||||
ECONNRESET :: Platform_Error.ECONNRESET
|
||||
ENOBUFS :: Platform_Error.ENOBUFS
|
||||
EISCONN :: Platform_Error.EISCONN
|
||||
ENOTCONN :: Platform_Error.ENOTCONN
|
||||
ESHUTDOWN :: Platform_Error.ESHUTDOWN
|
||||
ETIMEDOUT :: Platform_Error.ETIMEDOUT
|
||||
ECONNREFUSED :: Platform_Error.ECONNREFUSED
|
||||
ELOOP :: Platform_Error.ELOOP
|
||||
ENAMETOOLING :: Platform_Error.ENAMETOOLING
|
||||
EHOSTDOWN :: Platform_Error.EHOSTDOWN
|
||||
EHOSTUNREACH :: Platform_Error.EHOSTUNREACH
|
||||
ENOTEMPTY :: Platform_Error.ENOTEMPTY
|
||||
EPROCLIM :: Platform_Error.EPROCLIM
|
||||
EUSERS :: Platform_Error.EUSERS
|
||||
EDQUOT :: Platform_Error.EDQUOT
|
||||
ESTALE :: Platform_Error.ESTALE
|
||||
EBADRPC :: Platform_Error.EBADRPC
|
||||
ERPCMISMATCH :: Platform_Error.ERPCMISMATCH
|
||||
EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL
|
||||
EPROGMISMATCH :: Platform_Error.EPROGMISMATCH
|
||||
EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL
|
||||
ENOLCK :: Platform_Error.ENOLCK
|
||||
ENOSYS :: Platform_Error.ENOSYS
|
||||
EFTYPE :: Platform_Error.EFTYPE
|
||||
EAUTH :: Platform_Error.EAUTH
|
||||
ENEEDAUTH :: Platform_Error.ENEEDAUTH
|
||||
EIDRM :: Platform_Error.EIDRM
|
||||
ENOMSG :: Platform_Error.ENOMSG
|
||||
EOVERFLOW :: Platform_Error.EOVERFLOW
|
||||
ECANCELED :: Platform_Error.ECANCELED
|
||||
EILSEQ :: Platform_Error.EILSEQ
|
||||
ENOATTR :: Platform_Error.ENOATTR
|
||||
EDOOFUS :: Platform_Error.EDOOFUS
|
||||
EBADMSG :: Platform_Error.EBADMSG
|
||||
EMULTIHOP :: Platform_Error.EMULTIHOP
|
||||
ENOLINK :: Platform_Error.ENOLINK
|
||||
EPROTO :: Platform_Error.EPROTO
|
||||
ENOTCAPABLE :: Platform_Error.ENOTCAPABLE
|
||||
ECAPMODE :: Platform_Error.ECAPMODE
|
||||
ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
|
||||
EOWNERDEAD :: Platform_Error.EOWNERDEAD
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_NONBLOCK :: 0x00004
|
||||
O_APPEND :: 0x00008
|
||||
O_ASYNC :: 0x00040
|
||||
O_SYNC :: 0x00080
|
||||
O_CREATE :: 0x00200
|
||||
O_TRUNC :: 0x00400
|
||||
O_EXCL :: 0x00800
|
||||
O_NOCTTY :: 0x08000
|
||||
O_CLOEXEC :: 0100000
|
||||
|
||||
|
||||
SEEK_DATA :: 3
|
||||
SEEK_HOLE :: 4
|
||||
SEEK_MAX :: SEEK_HOLE
|
||||
|
||||
// NOTE: These are OS specific!
|
||||
// Do not mix these up!
|
||||
RTLD_LAZY :: 0x001
|
||||
RTLD_NOW :: 0x002
|
||||
//RTLD_BINDING_MASK :: 0x3 // Called MODEMASK in dlfcn.h
|
||||
RTLD_GLOBAL :: 0x100
|
||||
RTLD_LOCAL :: 0x000
|
||||
RTLD_TRACE :: 0x200
|
||||
RTLD_NODELETE :: 0x01000
|
||||
RTLD_NOLOAD :: 0x02000
|
||||
|
||||
MAX_PATH :: 1024
|
||||
|
||||
KINFO_FILE_SIZE :: 1392
|
||||
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
Unix_File_Time :: struct {
|
||||
seconds: time_t,
|
||||
nanoseconds: c.long,
|
||||
}
|
||||
|
||||
dev_t :: u64
|
||||
ino_t :: u64
|
||||
nlink_t :: u64
|
||||
off_t :: i64
|
||||
mode_t :: u16
|
||||
pid_t :: u32
|
||||
uid_t :: u32
|
||||
gid_t :: u32
|
||||
blkcnt_t :: i64
|
||||
blksize_t :: i32
|
||||
fflags_t :: u32
|
||||
|
||||
when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ {
|
||||
time_t :: i64
|
||||
} else {
|
||||
time_t :: i32
|
||||
}
|
||||
|
||||
|
||||
OS_Stat :: struct {
|
||||
device_id: dev_t,
|
||||
serial: ino_t,
|
||||
nlink: nlink_t,
|
||||
mode: mode_t,
|
||||
_padding0: i16,
|
||||
uid: uid_t,
|
||||
gid: gid_t,
|
||||
_padding1: i32,
|
||||
rdev: dev_t,
|
||||
|
||||
last_access: Unix_File_Time,
|
||||
modified: Unix_File_Time,
|
||||
status_change: Unix_File_Time,
|
||||
birthtime: Unix_File_Time,
|
||||
|
||||
size: off_t,
|
||||
blocks: blkcnt_t,
|
||||
block_size: blksize_t,
|
||||
|
||||
flags: fflags_t,
|
||||
gen: u64,
|
||||
lspare: [10]u64,
|
||||
}
|
||||
|
||||
KInfo_File :: struct {
|
||||
structsize: c.int,
|
||||
type: c.int,
|
||||
fd: c.int,
|
||||
ref_count: c.int,
|
||||
flags: c.int,
|
||||
pad0: c.int,
|
||||
offset: i64,
|
||||
|
||||
// NOTE(Feoramund): This field represents a complicated union that I am
|
||||
// avoiding implementing for now. I only need the path data below.
|
||||
_union: [336]byte,
|
||||
|
||||
path: [MAX_PATH]c.char,
|
||||
}
|
||||
|
||||
// since FreeBSD v12
|
||||
Dirent :: struct {
|
||||
ino: ino_t,
|
||||
off: off_t,
|
||||
reclen: u16,
|
||||
type: u8,
|
||||
_pad0: u8,
|
||||
namlen: u16,
|
||||
_pad1: u16,
|
||||
name: [256]byte,
|
||||
}
|
||||
|
||||
Dir :: distinct rawptr // DIR*
|
||||
|
||||
// File type
|
||||
S_IFMT :: 0o170000 // Type of file mask
|
||||
S_IFIFO :: 0o010000 // Named pipe (fifo)
|
||||
S_IFCHR :: 0o020000 // Character special
|
||||
S_IFDIR :: 0o040000 // Directory
|
||||
S_IFBLK :: 0o060000 // Block special
|
||||
S_IFREG :: 0o100000 // Regular
|
||||
S_IFLNK :: 0o120000 // Symbolic link
|
||||
S_IFSOCK :: 0o140000 // Socket
|
||||
//S_ISVTX :: 0o001000 // Save swapped text even after use
|
||||
|
||||
// File mode
|
||||
// Read, write, execute/search by owner
|
||||
S_IRWXU :: 0o0700 // RWX mask for owner
|
||||
S_IRUSR :: 0o0400 // R for owner
|
||||
S_IWUSR :: 0o0200 // W for owner
|
||||
S_IXUSR :: 0o0100 // X for owner
|
||||
|
||||
// Read, write, execute/search by group
|
||||
S_IRWXG :: 0o0070 // RWX mask for group
|
||||
S_IRGRP :: 0o0040 // R for group
|
||||
S_IWGRP :: 0o0020 // W for group
|
||||
S_IXGRP :: 0o0010 // X for group
|
||||
|
||||
// Read, write, execute/search by others
|
||||
S_IRWXO :: 0o0007 // RWX mask for other
|
||||
S_IROTH :: 0o0004 // R for other
|
||||
S_IWOTH :: 0o0002 // W for other
|
||||
S_IXOTH :: 0o0001 // X for other
|
||||
|
||||
S_ISUID :: 0o4000 // Set user id on execution
|
||||
S_ISGID :: 0o2000 // Set group id on execution
|
||||
S_ISVTX :: 0o1000 // Directory restrcted delete
|
||||
|
||||
|
||||
@(require_results) S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK }
|
||||
@(require_results) S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG }
|
||||
@(require_results) S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR }
|
||||
@(require_results) S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR }
|
||||
@(require_results) S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK }
|
||||
@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO }
|
||||
@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK }
|
||||
|
||||
F_OK :: 0 // Test for file existance
|
||||
X_OK :: 1 // Test for execute permission
|
||||
W_OK :: 2 // Test for write permission
|
||||
R_OK :: 4 // Test for read permission
|
||||
|
||||
F_KINFO :: 22
|
||||
|
||||
foreign libc {
|
||||
@(link_name="__error") __Error_location :: proc() -> ^c.int ---
|
||||
|
||||
@(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle ---
|
||||
@(link_name="close") _unix_close :: proc(fd: Handle) -> c.int ---
|
||||
@(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 ---
|
||||
@(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int ---
|
||||
@(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int ---
|
||||
@(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
|
||||
@(link_name="fstat") _unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> c.int ---
|
||||
@(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int ---
|
||||
@(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring ---
|
||||
@(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int ---
|
||||
@(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int ---
|
||||
@(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int ---
|
||||
@(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int ---
|
||||
@(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int ---
|
||||
@(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int ---
|
||||
@(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle ---
|
||||
|
||||
@(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir ---
|
||||
@(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int ---
|
||||
@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---
|
||||
@(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
|
||||
|
||||
@(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr ---
|
||||
@(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr ---
|
||||
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
|
||||
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
|
||||
|
||||
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
|
||||
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring ---
|
||||
@(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
|
||||
|
||||
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
|
||||
}
|
||||
foreign dl {
|
||||
@(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr ---
|
||||
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
|
||||
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
|
||||
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
|
||||
|
||||
@(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int ---
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_path_separator :: proc(r: rune) -> bool {
|
||||
return r == '/'
|
||||
}
|
||||
|
||||
@(require_results, no_instrumentation)
|
||||
get_last_error :: proc "contextless" () -> Error {
|
||||
return Platform_Error(__Error_location()^)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
handle := _unix_open(cstr, c.int(flags), u16(mode))
|
||||
if handle == -1 {
|
||||
return INVALID_HANDLE, get_last_error()
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
result := _unix_close(fd)
|
||||
if result == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> Error {
|
||||
return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd)
|
||||
}
|
||||
|
||||
// If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`.
|
||||
// In practice a read/write call would probably never read/write these big buffers all at once,
|
||||
// which is why the number of bytes is returned and why there are procs that will call this in a
|
||||
// loop for you.
|
||||
// We set a max of 1GB to keep alignment and to be safe.
|
||||
@(private)
|
||||
MAX_RW :: 1 << 30
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
to_read := min(c.size_t(len(data)), MAX_RW)
|
||||
bytes_read := _unix_read(fd, &data[0], to_read)
|
||||
if bytes_read == -1 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return int(bytes_read), nil
|
||||
}
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_write := min(c.size_t(len(data)), MAX_RW)
|
||||
bytes_written := _unix_write(fd, &data[0], to_write)
|
||||
if bytes_written == -1 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return int(bytes_written), nil
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_read := min(uint(len(data)), MAX_RW)
|
||||
|
||||
bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset)
|
||||
|
||||
return bytes_read, cast(_Platform_Error)errno
|
||||
}
|
||||
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_write := min(uint(len(data)), MAX_RW)
|
||||
|
||||
bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset)
|
||||
|
||||
return bytes_written, cast(_Platform_Error)errno
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
|
||||
switch whence {
|
||||
case SEEK_SET, SEEK_CUR, SEEK_END:
|
||||
break
|
||||
case:
|
||||
return 0, .Invalid_Whence
|
||||
}
|
||||
res := _unix_seek(fd, offset, c.int(whence))
|
||||
if res == -1 {
|
||||
errno := get_last_error()
|
||||
switch errno {
|
||||
case .EINVAL:
|
||||
return 0, .Invalid_Offset
|
||||
case:
|
||||
return 0, errno
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
|
||||
size = -1
|
||||
s := _fstat(fd) or_return
|
||||
size = s.size
|
||||
return
|
||||
}
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
||||
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
|
||||
res := _unix_rename(old_path_cstr, new_path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
remove :: proc(path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_unlink(path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_mkdir(path_cstr, mode)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
remove_directory :: proc(path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_rmdir(path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file_handle :: proc(fd: Handle) -> bool {
|
||||
s, err := _fstat(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISREG(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
|
||||
s: OS_Stat
|
||||
err: Error
|
||||
if follow_links {
|
||||
s, err = _stat(path)
|
||||
} else {
|
||||
s, err = _lstat(path)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISREG(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir_handle :: proc(fd: Handle) -> bool {
|
||||
s, err := _fstat(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISDIR(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
|
||||
s: OS_Stat
|
||||
err: Error
|
||||
if follow_links {
|
||||
s, err = _stat(path)
|
||||
} else {
|
||||
s, err = _lstat(path)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISDIR(s.mode)
|
||||
}
|
||||
|
||||
is_file :: proc {is_file_path, is_file_handle}
|
||||
is_dir :: proc {is_dir_path, is_dir_handle}
|
||||
|
||||
@(require_results)
|
||||
exists :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
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
|
||||
|
||||
stdin: Handle = 0
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
/* TODO(zangent): Implement these!
|
||||
last_write_time :: proc(fd: Handle) -> File_Time {}
|
||||
last_write_time_by_name :: proc(name: string) -> File_Time {}
|
||||
*/
|
||||
@(require_results)
|
||||
last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
|
||||
s, err := _fstat(fd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
|
||||
s, err := _stat(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
s: OS_Stat = ---
|
||||
result := _unix_lstat(cstr, &s)
|
||||
if result == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
res := _unix_lstat(cstr, &s)
|
||||
if res == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
s: OS_Stat = ---
|
||||
result := _unix_fstat(fd, &s)
|
||||
if result == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
|
||||
dirp := _unix_fdopendir(fd)
|
||||
if dirp == cast(Dir)nil {
|
||||
return nil, get_last_error()
|
||||
}
|
||||
return dirp, nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_closedir :: proc(dirp: Dir) -> Error {
|
||||
rc := _unix_closedir(dirp)
|
||||
if rc != 0 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_rewinddir :: proc(dirp: Dir) {
|
||||
_unix_rewinddir(dirp)
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
|
||||
result: ^Dirent
|
||||
rc := _unix_readdir_r(dirp, &entry, &result)
|
||||
|
||||
if rc != 0 {
|
||||
err = get_last_error()
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
end_of_stream = true
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_readlink :: proc(path: string) -> (string, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
|
||||
bufsz : uint = MAX_PATH
|
||||
buf := make([]byte, MAX_PATH)
|
||||
for {
|
||||
rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
|
||||
if rc == -1 {
|
||||
delete(buf)
|
||||
return "", get_last_error()
|
||||
} else if rc == int(bufsz) {
|
||||
bufsz += MAX_PATH
|
||||
delete(buf)
|
||||
buf = make([]byte, bufsz)
|
||||
} else {
|
||||
return strings.string_from_ptr(&buf[0], rc), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", Error{}
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_dup :: proc(fd: Handle) -> (Handle, Error) {
|
||||
dup := _unix_dup(fd)
|
||||
if dup == -1 {
|
||||
return INVALID_HANDLE, get_last_error()
|
||||
}
|
||||
return dup, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
|
||||
// NOTE(Feoramund): The situation isn't ideal, but this was the best way I
|
||||
// could find to implement this. There are a couple outstanding bug reports
|
||||
// regarding the desire to retrieve an absolute path from a handle, but to
|
||||
// my knowledge, there hasn't been any work done on it.
|
||||
//
|
||||
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570
|
||||
//
|
||||
// This may be unreliable, according to a comment from 2023.
|
||||
|
||||
kinfo: KInfo_File
|
||||
kinfo.structsize = KINFO_FILE_SIZE
|
||||
|
||||
res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo)
|
||||
if res == -1 {
|
||||
return "", get_last_error()
|
||||
}
|
||||
|
||||
path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path))
|
||||
return path, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
|
||||
rel := rel
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||
|
||||
path_ptr := _unix_realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", get_last_error()
|
||||
}
|
||||
defer _unix_free(rawptr(path_ptr))
|
||||
|
||||
return strings.clone(string(path_ptr), allocator)
|
||||
}
|
||||
|
||||
access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
result := _unix_access(cstr, c.int(mask))
|
||||
if result == -1 {
|
||||
return false, get_last_error()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
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 strings.clone(string(cstr), allocator), true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
buf[len(key)] = 0
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
// 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.
|
||||
page_size := get_page_size()
|
||||
buf := make([dynamic]u8, page_size)
|
||||
#no_bounds_check for {
|
||||
cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf)))
|
||||
if cwd != nil {
|
||||
return string(cwd)
|
||||
}
|
||||
if get_last_error() != ERANGE {
|
||||
delete(buf)
|
||||
return ""
|
||||
}
|
||||
resize(&buf, len(buf)+page_size)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
set_current_directory :: proc(path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_chdir(cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
runtime._cleanup_runtime_contextless()
|
||||
_unix_exit(c.int(code))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return cast(int) pthread_getthreadid_np()
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||
handle := _unix_dlopen(cstr, c.int(flags))
|
||||
return handle
|
||||
}
|
||||
@(require_results)
|
||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||
assert(handle != nil)
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
proc_handle := _unix_dlsym(handle, cstr)
|
||||
return proc_handle
|
||||
}
|
||||
dlclose :: proc(handle: rawptr) -> bool {
|
||||
assert(handle != nil)
|
||||
return _unix_dlclose(handle) == 0
|
||||
}
|
||||
dlerror :: proc() -> string {
|
||||
return string(_unix_dlerror())
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_page_size :: proc() -> int {
|
||||
// NOTE(tetra): The page size never changes, so why do anything complicated
|
||||
// if we don't have to.
|
||||
@static page_size := -1
|
||||
if page_size != -1 {
|
||||
return page_size
|
||||
}
|
||||
|
||||
page_size = int(_unix_getpagesize())
|
||||
return page_size
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
count : int = 0
|
||||
count_size := size_of(count)
|
||||
if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 {
|
||||
if count > 0 {
|
||||
return count
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
_alloc_command_line_arguments :: proc "contextless" () -> []string {
|
||||
context = runtime.default_context()
|
||||
res := make([]string, len(runtime.args__))
|
||||
for _, i in res {
|
||||
res[i] = string(runtime.args__[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@(private, fini)
|
||||
_delete_command_line_arguments :: proc "contextless" () {
|
||||
context = runtime.default_context()
|
||||
delete(args)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#+build freestanding
|
||||
package os_old
|
||||
|
||||
#panic("package os_old does not support a freestanding target")
|
||||
@@ -1,275 +0,0 @@
|
||||
#+build js
|
||||
package os_old
|
||||
|
||||
foreign import "odin_env"
|
||||
|
||||
@(require_results)
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/' || c == '\\'
|
||||
}
|
||||
|
||||
Handle :: distinct u32
|
||||
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
@(require_results)
|
||||
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> (err: Error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
foreign odin_env {
|
||||
@(link_name="write")
|
||||
_write :: proc "contextless" (fd: Handle, p: []byte) ---
|
||||
}
|
||||
_write(fd, data)
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
file_size :: proc(fd: Handle) -> (i64, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
exists :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir :: proc(path: string) -> bool {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
set_current_directory :: proc(path: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
change_directory :: proc(path: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
remove_directory :: proc(path: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
unlink :: proc(path: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
truncate :: proc(path: string, length: i64) -> (err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
remove :: proc(name: string) -> Error {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
File_Time :: distinct u64
|
||||
|
||||
_Platform_Error :: enum i32 {
|
||||
NONE = 0,
|
||||
FILE_NOT_FOUND = 2,
|
||||
PATH_NOT_FOUND = 3,
|
||||
ACCESS_DENIED = 5,
|
||||
INVALID_HANDLE = 6,
|
||||
NOT_ENOUGH_MEMORY = 8,
|
||||
NO_MORE_FILES = 18,
|
||||
HANDLE_EOF = 38,
|
||||
NETNAME_DELETED = 64,
|
||||
FILE_EXISTS = 80,
|
||||
INVALID_PARAMETER = 87,
|
||||
BROKEN_PIPE = 109,
|
||||
BUFFER_OVERFLOW = 111,
|
||||
INSUFFICIENT_BUFFER = 122,
|
||||
MOD_NOT_FOUND = 126,
|
||||
PROC_NOT_FOUND = 127,
|
||||
DIR_NOT_EMPTY = 145,
|
||||
ALREADY_EXISTS = 183,
|
||||
ENVVAR_NOT_FOUND = 203,
|
||||
MORE_DATA = 234,
|
||||
OPERATION_ABORTED = 995,
|
||||
IO_PENDING = 997,
|
||||
NOT_FOUND = 1168,
|
||||
PRIVILEGE_NOT_HELD = 1314,
|
||||
WSAEACCES = 10013,
|
||||
WSAECONNRESET = 10054,
|
||||
|
||||
// Windows reserves errors >= 1<<29 for application use
|
||||
FILE_IS_PIPE = 1<<29 + 0,
|
||||
FILE_IS_NOT_DIR = 1<<29 + 1,
|
||||
NEGATIVE_OFFSET = 1<<29 + 2,
|
||||
}
|
||||
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_CREATE :: 0x00040
|
||||
O_EXCL :: 0x00080
|
||||
O_NOCTTY :: 0x00100
|
||||
O_TRUNC :: 0x00200
|
||||
O_NONBLOCK :: 0x00800
|
||||
O_APPEND :: 0x00400
|
||||
O_SYNC :: 0x01000
|
||||
O_ASYNC :: 0x02000
|
||||
O_CLOEXEC :: 0x80000
|
||||
|
||||
|
||||
ERROR_FILE_NOT_FOUND :: Platform_Error.FILE_NOT_FOUND
|
||||
ERROR_PATH_NOT_FOUND :: Platform_Error.PATH_NOT_FOUND
|
||||
ERROR_ACCESS_DENIED :: Platform_Error.ACCESS_DENIED
|
||||
ERROR_INVALID_HANDLE :: Platform_Error.INVALID_HANDLE
|
||||
ERROR_NOT_ENOUGH_MEMORY :: Platform_Error.NOT_ENOUGH_MEMORY
|
||||
ERROR_NO_MORE_FILES :: Platform_Error.NO_MORE_FILES
|
||||
ERROR_HANDLE_EOF :: Platform_Error.HANDLE_EOF
|
||||
ERROR_NETNAME_DELETED :: Platform_Error.NETNAME_DELETED
|
||||
ERROR_FILE_EXISTS :: Platform_Error.FILE_EXISTS
|
||||
ERROR_INVALID_PARAMETER :: Platform_Error.INVALID_PARAMETER
|
||||
ERROR_BROKEN_PIPE :: Platform_Error.BROKEN_PIPE
|
||||
ERROR_BUFFER_OVERFLOW :: Platform_Error.BUFFER_OVERFLOW
|
||||
ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER
|
||||
ERROR_MOD_NOT_FOUND :: Platform_Error.MOD_NOT_FOUND
|
||||
ERROR_PROC_NOT_FOUND :: Platform_Error.PROC_NOT_FOUND
|
||||
ERROR_DIR_NOT_EMPTY :: Platform_Error.DIR_NOT_EMPTY
|
||||
ERROR_ALREADY_EXISTS :: Platform_Error.ALREADY_EXISTS
|
||||
ERROR_ENVVAR_NOT_FOUND :: Platform_Error.ENVVAR_NOT_FOUND
|
||||
ERROR_MORE_DATA :: Platform_Error.MORE_DATA
|
||||
ERROR_OPERATION_ABORTED :: Platform_Error.OPERATION_ABORTED
|
||||
ERROR_IO_PENDING :: Platform_Error.IO_PENDING
|
||||
ERROR_NOT_FOUND :: Platform_Error.NOT_FOUND
|
||||
ERROR_PRIVILEGE_NOT_HELD :: Platform_Error.PRIVILEGE_NOT_HELD
|
||||
WSAEACCES :: Platform_Error.WSAEACCES
|
||||
WSAECONNRESET :: Platform_Error.WSAECONNRESET
|
||||
|
||||
ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe
|
||||
ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir
|
||||
|
||||
args: []string
|
||||
|
||||
@(require_results)
|
||||
last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
get_page_size :: proc() -> int {
|
||||
unimplemented("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
return 1
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
unimplemented_contextless("core:os procedure not supported on JS target")
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,932 +0,0 @@
|
||||
package os_old
|
||||
|
||||
foreign import libc "system:c"
|
||||
|
||||
import "core:strings"
|
||||
import "core:c"
|
||||
import "base:runtime"
|
||||
|
||||
Handle :: distinct i32
|
||||
Pid :: distinct i32
|
||||
File_Time :: distinct u64
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
_Platform_Error :: enum i32 {
|
||||
NONE = 0,
|
||||
EPERM = 1,
|
||||
ENOENT = 2,
|
||||
ESRCH = 3,
|
||||
EINTR = 4,
|
||||
EIO = 5,
|
||||
ENXIO = 6,
|
||||
E2BIG = 7,
|
||||
ENOEXEC = 8,
|
||||
EBADF = 9,
|
||||
ECHILD = 10,
|
||||
EDEADLK = 11,
|
||||
ENOMEM = 12,
|
||||
EACCES = 13,
|
||||
EFAULT = 14,
|
||||
ENOTBLK = 15,
|
||||
EBUSY = 16,
|
||||
EEXIST = 17,
|
||||
EXDEV = 18,
|
||||
ENODEV = 19,
|
||||
ENOTDIR = 20,
|
||||
EISDIR = 21,
|
||||
EINVAL = 22,
|
||||
ENFILE = 23,
|
||||
EMFILE = 24,
|
||||
ENOTTY = 25,
|
||||
ETXTBSY = 26,
|
||||
EFBIG = 27,
|
||||
ENOSPC = 28,
|
||||
ESPIPE = 29,
|
||||
EROFS = 30,
|
||||
EMLINK = 31,
|
||||
EPIPE = 32,
|
||||
EDOM = 33,
|
||||
ERANGE = 34,
|
||||
EAGAIN = 35,
|
||||
EWOULDBLOCK = EAGAIN,
|
||||
EINPROGRESS = 36,
|
||||
EALREADY = 37,
|
||||
ENOTSOCK = 38,
|
||||
EDESTADDRREQ = 39,
|
||||
EMSGSIZE = 40,
|
||||
EPROTOTYPE = 41,
|
||||
ENOPROTOOPT = 42,
|
||||
EPROTONOSUPPORT = 43,
|
||||
ESOCKTNOSUPPORT = 44,
|
||||
EOPNOTSUPP = 45,
|
||||
EPFNOSUPPORT = 46,
|
||||
EAFNOSUPPORT = 47,
|
||||
EADDRINUSE = 48,
|
||||
EADDRNOTAVAIL = 49,
|
||||
ENETDOWN = 50,
|
||||
ENETUNREACH = 51,
|
||||
ENETRESET = 52,
|
||||
ECONNABORTED = 53,
|
||||
ECONNRESET = 54,
|
||||
ENOBUFS = 55,
|
||||
EISCONN = 56,
|
||||
ENOTCONN = 57,
|
||||
ESHUTDOWN = 58,
|
||||
ETOOMANYREFS = 59,
|
||||
ETIMEDOUT = 60,
|
||||
ECONNREFUSED = 61,
|
||||
ELOOP = 62,
|
||||
ENAMETOOLONG = 63,
|
||||
EHOSTDOWN = 64,
|
||||
EHOSTUNREACH = 65,
|
||||
ENOTEMPTY = 66,
|
||||
EPROCLIM = 67,
|
||||
EUSERS = 68,
|
||||
EDQUOT = 69,
|
||||
ESTALE = 70,
|
||||
EREMOTE = 71,
|
||||
EBADRPC = 72,
|
||||
ERPCMISMATCH = 73,
|
||||
EPROGUNAVAIL = 74,
|
||||
EPROGMISMATCH = 75,
|
||||
EPROCUNAVAIL = 76,
|
||||
ENOLCK = 77,
|
||||
ENOSYS = 78,
|
||||
EFTYPE = 79,
|
||||
EAUTH = 80,
|
||||
ENEEDAUTH = 81,
|
||||
EIPSEC = 82,
|
||||
ENOATTR = 83,
|
||||
EILSEQ = 84,
|
||||
ENOMEDIUM = 85,
|
||||
EMEDIUMTYPE = 86,
|
||||
EOVERFLOW = 87,
|
||||
ECANCELED = 88,
|
||||
EIDRM = 89,
|
||||
ENOMSG = 90,
|
||||
ENOTSUP = 91,
|
||||
EBADMSG = 92,
|
||||
ENOTRECOVERABLE = 93,
|
||||
EOWNERDEAD = 94,
|
||||
EPROTO = 95,
|
||||
}
|
||||
|
||||
EPERM :: Platform_Error.EPERM
|
||||
ENOENT :: Platform_Error.ENOENT
|
||||
ESRCH :: Platform_Error.ESRCH
|
||||
EINTR :: Platform_Error.EINTR
|
||||
EIO :: Platform_Error.EIO
|
||||
ENXIO :: Platform_Error.ENXIO
|
||||
E2BIG :: Platform_Error.E2BIG
|
||||
ENOEXEC :: Platform_Error.ENOEXEC
|
||||
EBADF :: Platform_Error.EBADF
|
||||
ECHILD :: Platform_Error.ECHILD
|
||||
EDEADLK :: Platform_Error.EDEADLK
|
||||
ENOMEM :: Platform_Error.ENOMEM
|
||||
EACCES :: Platform_Error.EACCES
|
||||
EFAULT :: Platform_Error.EFAULT
|
||||
ENOTBLK :: Platform_Error.ENOTBLK
|
||||
EBUSY :: Platform_Error.EBUSY
|
||||
EEXIST :: Platform_Error.EEXIST
|
||||
EXDEV :: Platform_Error.EXDEV
|
||||
ENODEV :: Platform_Error.ENODEV
|
||||
ENOTDIR :: Platform_Error.ENOTDIR
|
||||
EISDIR :: Platform_Error.EISDIR
|
||||
EINVAL :: Platform_Error.EINVAL
|
||||
ENFILE :: Platform_Error.ENFILE
|
||||
EMFILE :: Platform_Error.EMFILE
|
||||
ENOTTY :: Platform_Error.ENOTTY
|
||||
ETXTBSY :: Platform_Error.ETXTBSY
|
||||
EFBIG :: Platform_Error.EFBIG
|
||||
ENOSPC :: Platform_Error.ENOSPC
|
||||
ESPIPE :: Platform_Error.ESPIPE
|
||||
EROFS :: Platform_Error.EROFS
|
||||
EMLINK :: Platform_Error.EMLINK
|
||||
EPIPE :: Platform_Error.EPIPE
|
||||
EDOM :: Platform_Error.EDOM
|
||||
ERANGE :: Platform_Error.ERANGE
|
||||
EAGAIN :: Platform_Error.EAGAIN
|
||||
EWOULDBLOCK :: Platform_Error.EWOULDBLOCK
|
||||
EINPROGRESS :: Platform_Error.EINPROGRESS
|
||||
EALREADY :: Platform_Error.EALREADY
|
||||
ENOTSOCK :: Platform_Error.ENOTSOCK
|
||||
EDESTADDRREQ :: Platform_Error.EDESTADDRREQ
|
||||
EMSGSIZE :: Platform_Error.EMSGSIZE
|
||||
EPROTOTYPE :: Platform_Error.EPROTOTYPE
|
||||
ENOPROTOOPT :: Platform_Error.ENOPROTOOPT
|
||||
EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT
|
||||
ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT
|
||||
EOPNOTSUPP :: Platform_Error.EOPNOTSUPP
|
||||
EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT
|
||||
EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT
|
||||
EADDRINUSE :: Platform_Error.EADDRINUSE
|
||||
EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL
|
||||
ENETDOWN :: Platform_Error.ENETDOWN
|
||||
ENETUNREACH :: Platform_Error.ENETUNREACH
|
||||
ENETRESET :: Platform_Error.ENETRESET
|
||||
ECONNABORTED :: Platform_Error.ECONNABORTED
|
||||
ECONNRESET :: Platform_Error.ECONNRESET
|
||||
ENOBUFS :: Platform_Error.ENOBUFS
|
||||
EISCONN :: Platform_Error.EISCONN
|
||||
ENOTCONN :: Platform_Error.ENOTCONN
|
||||
ESHUTDOWN :: Platform_Error.ESHUTDOWN
|
||||
ETOOMANYREFS :: Platform_Error.ETOOMANYREFS
|
||||
ETIMEDOUT :: Platform_Error.ETIMEDOUT
|
||||
ECONNREFUSED :: Platform_Error.ECONNREFUSED
|
||||
ELOOP :: Platform_Error.ELOOP
|
||||
ENAMETOOLONG :: Platform_Error.ENAMETOOLONG
|
||||
EHOSTDOWN :: Platform_Error.EHOSTDOWN
|
||||
EHOSTUNREACH :: Platform_Error.EHOSTUNREACH
|
||||
ENOTEMPTY :: Platform_Error.ENOTEMPTY
|
||||
EPROCLIM :: Platform_Error.EPROCLIM
|
||||
EUSERS :: Platform_Error.EUSERS
|
||||
EDQUOT :: Platform_Error.EDQUOT
|
||||
ESTALE :: Platform_Error.ESTALE
|
||||
EREMOTE :: Platform_Error.EREMOTE
|
||||
EBADRPC :: Platform_Error.EBADRPC
|
||||
ERPCMISMATCH :: Platform_Error.ERPCMISMATCH
|
||||
EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL
|
||||
EPROGMISMATCH :: Platform_Error.EPROGMISMATCH
|
||||
EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL
|
||||
ENOLCK :: Platform_Error.ENOLCK
|
||||
ENOSYS :: Platform_Error.ENOSYS
|
||||
EFTYPE :: Platform_Error.EFTYPE
|
||||
EAUTH :: Platform_Error.EAUTH
|
||||
ENEEDAUTH :: Platform_Error.ENEEDAUTH
|
||||
EIPSEC :: Platform_Error.EIPSEC
|
||||
ENOATTR :: Platform_Error.ENOATTR
|
||||
EILSEQ :: Platform_Error.EILSEQ
|
||||
ENOMEDIUM :: Platform_Error.ENOMEDIUM
|
||||
EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE
|
||||
EOVERFLOW :: Platform_Error.EOVERFLOW
|
||||
ECANCELED :: Platform_Error.ECANCELED
|
||||
EIDRM :: Platform_Error.EIDRM
|
||||
ENOMSG :: Platform_Error.ENOMSG
|
||||
ENOTSUP :: Platform_Error.ENOTSUP
|
||||
EBADMSG :: Platform_Error.EBADMSG
|
||||
ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE
|
||||
EOWNERDEAD :: Platform_Error.EOWNERDEAD
|
||||
EPROTO :: Platform_Error.EPROTO
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_NONBLOCK :: 0x00004
|
||||
O_APPEND :: 0x00008
|
||||
O_ASYNC :: 0x00040
|
||||
O_SYNC :: 0x00080
|
||||
O_CREATE :: 0x00200
|
||||
O_TRUNC :: 0x00400
|
||||
O_EXCL :: 0x00800
|
||||
O_NOCTTY :: 0x08000
|
||||
O_CLOEXEC :: 0x10000
|
||||
|
||||
RTLD_LAZY :: 0x001
|
||||
RTLD_NOW :: 0x002
|
||||
RTLD_LOCAL :: 0x000
|
||||
RTLD_GLOBAL :: 0x100
|
||||
RTLD_TRACE :: 0x200
|
||||
RTLD_NODELETE :: 0x400
|
||||
|
||||
MAX_PATH :: 1024
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
pid_t :: i32
|
||||
time_t :: i64
|
||||
mode_t :: u32
|
||||
dev_t :: i32
|
||||
ino_t :: u64
|
||||
nlink_t :: u32
|
||||
uid_t :: u32
|
||||
gid_t :: u32
|
||||
off_t :: i64
|
||||
blkcnt_t :: u64
|
||||
blksize_t :: i32
|
||||
|
||||
Unix_File_Time :: struct {
|
||||
seconds: time_t,
|
||||
nanoseconds: c.long,
|
||||
}
|
||||
|
||||
OS_Stat :: struct {
|
||||
mode: mode_t, // inode protection mode
|
||||
device_id: dev_t, // inode's device
|
||||
serial: ino_t, // inode's number
|
||||
nlink: nlink_t, // number of hard links
|
||||
uid: uid_t, // user ID of the file's owner
|
||||
gid: gid_t, // group ID of the file's group
|
||||
rdev: dev_t, // device type
|
||||
|
||||
last_access: Unix_File_Time, // time of last access
|
||||
modified: Unix_File_Time, // time of last data modification
|
||||
status_change: Unix_File_Time, // time of last file status change
|
||||
|
||||
size: off_t, // file size, in bytes
|
||||
blocks: blkcnt_t, // blocks allocated for file
|
||||
block_size: blksize_t, // optimal blocksize for I/O
|
||||
|
||||
flags: u32, // user defined flags for file
|
||||
gen: u32, // file generation number
|
||||
birthtime: Unix_File_Time, // time of file creation
|
||||
}
|
||||
|
||||
MAXNAMLEN :: 255
|
||||
|
||||
// NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above
|
||||
Dirent :: struct {
|
||||
ino: ino_t, // file number of entry
|
||||
off: off_t, // offset after this entry
|
||||
reclen: u16, // length of this record
|
||||
type: u8, // file type
|
||||
namlen: u8, // length of string in name
|
||||
_padding: [4]u8,
|
||||
name: [MAXNAMLEN + 1]byte, // name
|
||||
}
|
||||
|
||||
Dir :: distinct rawptr // DIR*
|
||||
|
||||
// File type
|
||||
S_IFMT :: 0o170000 // Type of file mask
|
||||
S_IFIFO :: 0o010000 // Named pipe (fifo)
|
||||
S_IFCHR :: 0o020000 // Character special
|
||||
S_IFDIR :: 0o040000 // Directory
|
||||
S_IFBLK :: 0o060000 // Block special
|
||||
S_IFREG :: 0o100000 // Regular
|
||||
S_IFLNK :: 0o120000 // Symbolic link
|
||||
S_IFSOCK :: 0o140000 // Socket
|
||||
S_ISVTX :: 0o001000 // Save swapped text even after use
|
||||
|
||||
// File mode
|
||||
// Read, write, execute/search by owner
|
||||
S_IRWXU :: 0o0700 // RWX mask for owner
|
||||
S_IRUSR :: 0o0400 // R for owner
|
||||
S_IWUSR :: 0o0200 // W for owner
|
||||
S_IXUSR :: 0o0100 // X for owner
|
||||
|
||||
// Read, write, execute/search by group
|
||||
S_IRWXG :: 0o0070 // RWX mask for group
|
||||
S_IRGRP :: 0o0040 // R for group
|
||||
S_IWGRP :: 0o0020 // W for group
|
||||
S_IXGRP :: 0o0010 // X for group
|
||||
|
||||
// Read, write, execute/search by others
|
||||
S_IRWXO :: 0o0007 // RWX mask for other
|
||||
S_IROTH :: 0o0004 // R for other
|
||||
S_IWOTH :: 0o0002 // W for other
|
||||
S_IXOTH :: 0o0001 // X for other
|
||||
|
||||
S_ISUID :: 0o4000 // Set user id on execution
|
||||
S_ISGID :: 0o2000 // Set group id on execution
|
||||
S_ISTXT :: 0o1000 // Sticky bit
|
||||
|
||||
@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK }
|
||||
@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG }
|
||||
@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR }
|
||||
@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR }
|
||||
@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK }
|
||||
@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO }
|
||||
@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK }
|
||||
|
||||
F_OK :: 0x00 // Test for file existance
|
||||
X_OK :: 0x01 // Test for execute permission
|
||||
W_OK :: 0x02 // Test for write permission
|
||||
R_OK :: 0x04 // Test for read permission
|
||||
|
||||
AT_FDCWD :: -100
|
||||
AT_EACCESS :: 0x01
|
||||
AT_SYMLINK_NOFOLLOW :: 0x02
|
||||
AT_SYMLINK_FOLLOW :: 0x04
|
||||
AT_REMOVEDIR :: 0x08
|
||||
|
||||
@(default_calling_convention="c")
|
||||
foreign libc {
|
||||
@(link_name="__errno") __error :: proc() -> ^c.int ---
|
||||
|
||||
@(link_name="fork") _unix_fork :: proc() -> pid_t ---
|
||||
@(link_name="getthrid") _unix_getthrid :: proc() -> int ---
|
||||
|
||||
@(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle ---
|
||||
@(link_name="close") _unix_close :: proc(fd: Handle) -> c.int ---
|
||||
@(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t ---
|
||||
@(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t ---
|
||||
@(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t ---
|
||||
@(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
|
||||
@(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int ---
|
||||
@(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int ---
|
||||
@(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t ---
|
||||
@(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int ---
|
||||
@(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring ---
|
||||
@(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int ---
|
||||
@(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int ---
|
||||
@(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int ---
|
||||
@(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int ---
|
||||
@(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int ---
|
||||
@(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int ---
|
||||
@(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle ---
|
||||
|
||||
@(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int ---
|
||||
@(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long ---
|
||||
@(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir ---
|
||||
@(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int ---
|
||||
@(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) ---
|
||||
@(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int ---
|
||||
|
||||
@(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr ---
|
||||
@(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr ---
|
||||
@(link_name="free") _unix_free :: proc(ptr: rawptr) ---
|
||||
@(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr ---
|
||||
|
||||
@(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring ---
|
||||
@(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring ---
|
||||
|
||||
@(link_name="exit") _unix_exit :: proc(status: c.int) -> ! ---
|
||||
|
||||
@(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr ---
|
||||
@(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr ---
|
||||
@(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int ---
|
||||
@(link_name="dlerror") _unix_dlerror :: proc() -> cstring ---
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_path_separator :: proc(r: rune) -> bool {
|
||||
return r == '/'
|
||||
}
|
||||
|
||||
@(require_results, no_instrumentation)
|
||||
get_last_error :: proc "contextless" () -> Error {
|
||||
return Platform_Error(__error()^)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fork :: proc() -> (Pid, Error) {
|
||||
pid := _unix_fork()
|
||||
if pid == -1 {
|
||||
return Pid(-1), get_last_error()
|
||||
}
|
||||
return Pid(pid), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
handle := _unix_open(cstr, c.int(flags), c.uint(mode))
|
||||
if handle == -1 {
|
||||
return INVALID_HANDLE, get_last_error()
|
||||
}
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
result := _unix_close(fd)
|
||||
if result == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> Error {
|
||||
result := _unix_fsync(fd)
|
||||
if result == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`.
|
||||
// In practice a read/write call would probably never read/write these big buffers all at once,
|
||||
// which is why the number of bytes is returned and why there are procs that will call this in a
|
||||
// loop for you.
|
||||
// We set a max of 1GB to keep alignment and to be safe.
|
||||
@(private)
|
||||
MAX_RW :: 1 << 30
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
to_read := min(c.size_t(len(data)), MAX_RW)
|
||||
bytes_read := _unix_read(fd, &data[0], to_read)
|
||||
if bytes_read == -1 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return int(bytes_read), nil
|
||||
}
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_write := min(c.size_t(len(data)), MAX_RW)
|
||||
bytes_written := _unix_write(fd, &data[0], to_write)
|
||||
if bytes_written == -1 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return int(bytes_written), nil
|
||||
}
|
||||
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_read := min(uint(len(data)), MAX_RW)
|
||||
|
||||
bytes_read := _unix_pread(fd, raw_data(data), to_read, offset)
|
||||
if bytes_read < 0 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return bytes_read, nil
|
||||
}
|
||||
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
to_write := min(uint(len(data)), MAX_RW)
|
||||
|
||||
bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset)
|
||||
if bytes_written < 0 {
|
||||
return -1, get_last_error()
|
||||
}
|
||||
return bytes_written, nil
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
|
||||
switch whence {
|
||||
case SEEK_SET, SEEK_CUR, SEEK_END:
|
||||
break
|
||||
case:
|
||||
return 0, .Invalid_Whence
|
||||
}
|
||||
res := _unix_seek(fd, offset, c.int(whence))
|
||||
if res == -1 {
|
||||
errno := get_last_error()
|
||||
switch errno {
|
||||
case .EINVAL:
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
return 0, errno
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
file_size :: proc(fd: Handle) -> (size: i64, err: Error) {
|
||||
size = -1
|
||||
s := _fstat(fd) or_return
|
||||
size = s.size
|
||||
return
|
||||
}
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator)
|
||||
new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator)
|
||||
res := _unix_rename(old_path_cstr, new_path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
remove :: proc(path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_unlink(path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_mkdir(path_cstr, mode)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
remove_directory :: proc(path: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_rmdir(path_cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file_handle :: proc(fd: Handle) -> bool {
|
||||
s, err := _fstat(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISREG(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file_path :: proc(path: string, follow_links: bool = true) -> bool {
|
||||
s: OS_Stat
|
||||
err: Error
|
||||
if follow_links {
|
||||
s, err = _stat(path)
|
||||
} else {
|
||||
s, err = _lstat(path)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISREG(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir_handle :: proc(fd: Handle) -> bool {
|
||||
s, err := _fstat(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISDIR(s.mode)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir_path :: proc(path: string, follow_links: bool = true) -> bool {
|
||||
s: OS_Stat
|
||||
err: Error
|
||||
if follow_links {
|
||||
s, err = _stat(path)
|
||||
} else {
|
||||
s, err = _lstat(path)
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return S_ISDIR(s.mode)
|
||||
}
|
||||
|
||||
is_file :: proc {is_file_path, is_file_handle}
|
||||
is_dir :: proc {is_dir_path, is_dir_handle}
|
||||
|
||||
// NOTE(bill): Uses startup to initialize it
|
||||
|
||||
stdin: Handle = 0
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
/* TODO(zangent): Implement these!
|
||||
last_write_time :: proc(fd: Handle) -> File_Time {}
|
||||
last_write_time_by_name :: proc(name: string) -> File_Time {}
|
||||
*/
|
||||
@(require_results)
|
||||
last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) {
|
||||
s := _fstat(fd) or_return
|
||||
modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
|
||||
s := _stat(name) or_return
|
||||
modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds
|
||||
return File_Time(modified), nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_stat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
res := _unix_stat(cstr, &s)
|
||||
if res == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_lstat :: proc(path: string) -> (OS_Stat, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
res := _unix_lstat(cstr, &s)
|
||||
if res == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results, no_sanitize_memory)
|
||||
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
|
||||
// deliberately uninitialized
|
||||
s: OS_Stat = ---
|
||||
res := _unix_fstat(fd, &s)
|
||||
if res == -1 {
|
||||
return s, get_last_error()
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_fdopendir :: proc(fd: Handle) -> (Dir, Error) {
|
||||
dirp := _unix_fdopendir(fd)
|
||||
if dirp == cast(Dir)nil {
|
||||
return nil, get_last_error()
|
||||
}
|
||||
return dirp, nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_closedir :: proc(dirp: Dir) -> Error {
|
||||
rc := _unix_closedir(dirp)
|
||||
if rc != 0 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_rewinddir :: proc(dirp: Dir) {
|
||||
_unix_rewinddir(dirp)
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) {
|
||||
result: ^Dirent
|
||||
rc := _unix_readdir_r(dirp, &entry, &result)
|
||||
|
||||
if rc != 0 {
|
||||
err = get_last_error()
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
if result == nil {
|
||||
end_of_stream = true
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_readlink :: proc(path: string) -> (string, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
path_cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
|
||||
bufsz : uint = MAX_PATH
|
||||
buf := make([]byte, MAX_PATH)
|
||||
for {
|
||||
rc := _unix_readlink(path_cstr, &(buf[0]), bufsz)
|
||||
if rc == -1 {
|
||||
delete(buf)
|
||||
return "", get_last_error()
|
||||
} else if rc == int(bufsz) {
|
||||
bufsz += MAX_PATH
|
||||
delete(buf)
|
||||
buf = make([]byte, bufsz)
|
||||
} else {
|
||||
return strings.string_from_ptr(&buf[0], rc), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_dup :: proc(fd: Handle) -> (Handle, Error) {
|
||||
dup := _unix_dup(fd)
|
||||
if dup == -1 {
|
||||
return INVALID_HANDLE, get_last_error()
|
||||
}
|
||||
return dup, nil
|
||||
}
|
||||
|
||||
// XXX OpenBSD
|
||||
@(require_results)
|
||||
absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) {
|
||||
return "", Error(ENOSYS)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) {
|
||||
rel := rel
|
||||
if rel == "" {
|
||||
rel = "."
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
|
||||
|
||||
path_ptr := _unix_realpath(rel_cstr, nil)
|
||||
if path_ptr == nil {
|
||||
return "", get_last_error()
|
||||
}
|
||||
defer _unix_free(rawptr(path_ptr))
|
||||
|
||||
return strings.clone(string(path_ptr), allocator)
|
||||
}
|
||||
|
||||
access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_access(cstr, c.int(mask))
|
||||
if res == -1 {
|
||||
return false, get_last_error()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
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 strings.clone(string(cstr), allocator), true
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
buf[len(key)] = 0
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
buf := make([dynamic]u8, MAX_PATH)
|
||||
for {
|
||||
cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf)))
|
||||
if cwd != nil {
|
||||
return string(cwd)
|
||||
}
|
||||
if get_last_error() != ERANGE {
|
||||
delete(buf)
|
||||
return ""
|
||||
}
|
||||
resize(&buf, len(buf) + MAX_PATH)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
set_current_directory :: proc(path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(path, context.temp_allocator)
|
||||
res := _unix_chdir(cstr)
|
||||
if res == -1 {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
runtime._cleanup_runtime_contextless()
|
||||
_unix_exit(c.int(code))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return _unix_getthrid()
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
dlopen :: proc(filename: string, flags: int) -> rawptr {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(filename, context.temp_allocator)
|
||||
handle := _unix_dlopen(cstr, c.int(flags))
|
||||
return handle
|
||||
}
|
||||
@(require_results)
|
||||
dlsym :: proc(handle: rawptr, symbol: string) -> rawptr {
|
||||
assert(handle != nil)
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
cstr := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
proc_handle := _unix_dlsym(handle, cstr)
|
||||
return proc_handle
|
||||
}
|
||||
dlclose :: proc(handle: rawptr) -> bool {
|
||||
assert(handle != nil)
|
||||
return _unix_dlclose(handle) == 0
|
||||
}
|
||||
@(require_results)
|
||||
dlerror :: proc() -> string {
|
||||
return string(_unix_dlerror())
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_page_size :: proc() -> int {
|
||||
// NOTE(tetra): The page size never changes, so why do anything complicated
|
||||
// if we don't have to.
|
||||
@static page_size := -1
|
||||
if page_size != -1 {
|
||||
return page_size
|
||||
}
|
||||
|
||||
page_size = int(_unix_getpagesize())
|
||||
return page_size
|
||||
}
|
||||
|
||||
_SC_NPROCESSORS_ONLN :: 503
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
return int(_sysconf(_SC_NPROCESSORS_ONLN))
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_alloc_command_line_arguments :: proc "contextless" () -> []string {
|
||||
context = runtime.default_context()
|
||||
res := make([]string, len(runtime.args__))
|
||||
for _, i in res {
|
||||
res[i] = string(runtime.args__[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@(private, fini)
|
||||
_delete_command_line_arguments :: proc "contextless" () {
|
||||
context = runtime.default_context()
|
||||
delete(args)
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import "core:sys/wasm/wasi"
|
||||
import "base:runtime"
|
||||
|
||||
Handle :: distinct i32
|
||||
_Platform_Error :: wasi.errno_t
|
||||
|
||||
INVALID_HANDLE :: -1
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_CREATE :: 0x00040
|
||||
O_EXCL :: 0x00080
|
||||
O_NOCTTY :: 0x00100
|
||||
O_TRUNC :: 0x00200
|
||||
O_NONBLOCK :: 0x00800
|
||||
O_APPEND :: 0x00400
|
||||
O_SYNC :: 0x01000
|
||||
O_ASYNC :: 0x02000
|
||||
O_CLOEXEC :: 0x80000
|
||||
|
||||
stdin: Handle = 0
|
||||
stdout: Handle = 1
|
||||
stderr: Handle = 2
|
||||
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
@(private, require_results)
|
||||
_alloc_command_line_arguments :: proc "contextless" () -> []string {
|
||||
context = runtime.default_context()
|
||||
cmd_args := make([]string, len(runtime.args__))
|
||||
for &arg, i in cmd_args {
|
||||
arg = string(runtime.args__[i])
|
||||
}
|
||||
return cmd_args
|
||||
}
|
||||
|
||||
@(private, fini)
|
||||
_delete_command_line_arguments :: proc "contextless" () {
|
||||
context = runtime.default_context()
|
||||
delete(args)
|
||||
}
|
||||
|
||||
// WASI works with "preopened" directories, the environment retrieves directories
|
||||
// (for example with `wasmtime --dir=. module.wasm`) and those given directories
|
||||
// are the only ones accessible by the application.
|
||||
//
|
||||
// So in order to facilitate the `os` API (absolute paths etc.) we keep a list
|
||||
// of the given directories and match them when needed (notably `os.open`).
|
||||
|
||||
@(private)
|
||||
Preopen :: struct {
|
||||
fd: wasi.fd_t,
|
||||
prefix: string,
|
||||
}
|
||||
@(private)
|
||||
preopens: []Preopen
|
||||
|
||||
@(init, private)
|
||||
init_preopens :: proc "contextless" () {
|
||||
strip_prefixes :: proc "contextless"(path: string) -> string {
|
||||
path := path
|
||||
loop: for len(path) > 0 {
|
||||
switch {
|
||||
case path[0] == '/':
|
||||
path = path[1:]
|
||||
case len(path) > 2 && path[0] == '.' && path[1] == '/':
|
||||
path = path[2:]
|
||||
case len(path) == 1 && path[0] == '.':
|
||||
path = path[1:]
|
||||
case:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
context = runtime.default_context()
|
||||
|
||||
dyn_preopens: [dynamic]Preopen
|
||||
loop: for fd := wasi.fd_t(3); ; fd += 1 {
|
||||
desc, err := wasi.fd_prestat_get(fd)
|
||||
#partial switch err {
|
||||
case .BADF: break loop
|
||||
case: panic("fd_prestat_get returned an unexpected error")
|
||||
case .SUCCESS:
|
||||
}
|
||||
|
||||
switch desc.tag {
|
||||
case .DIR:
|
||||
buf := make([]byte, desc.dir.pr_name_len) or_else panic("could not allocate memory for filesystem preopens")
|
||||
if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS {
|
||||
panic("could not get filesystem preopen dir name")
|
||||
}
|
||||
append(&dyn_preopens, Preopen{fd, strip_prefixes(string(buf))})
|
||||
}
|
||||
}
|
||||
preopens = dyn_preopens[:]
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) {
|
||||
@(require_results)
|
||||
prefix_matches :: proc(prefix, path: string) -> bool {
|
||||
// Empty is valid for any relative path.
|
||||
if len(prefix) == 0 && len(path) > 0 && path[0] != '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(path) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
|
||||
if path[:len(prefix)] != prefix {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only match on full components.
|
||||
i := len(prefix)
|
||||
for i > 0 && prefix[i-1] == '/' {
|
||||
i -= 1
|
||||
}
|
||||
return path[i] == '/'
|
||||
}
|
||||
|
||||
path := path
|
||||
for len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
match: Preopen
|
||||
#reverse for preopen in preopens {
|
||||
if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) {
|
||||
match = preopen
|
||||
}
|
||||
}
|
||||
|
||||
if match.fd == 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
relative := path[len(match.prefix):]
|
||||
for len(relative) > 0 && relative[0] == '/' {
|
||||
relative = relative[1:]
|
||||
}
|
||||
|
||||
if len(relative) == 0 {
|
||||
relative = "."
|
||||
}
|
||||
|
||||
return match.fd, relative, true
|
||||
}
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
iovs := wasi.ciovec_t(data)
|
||||
n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
|
||||
return int(n), Platform_Error(err)
|
||||
}
|
||||
read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
iovs := wasi.iovec_t(data)
|
||||
n, err := wasi.fd_read(wasi.fd_t(fd), {iovs})
|
||||
return int(n), Platform_Error(err)
|
||||
}
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||
iovs := wasi.ciovec_t(data)
|
||||
n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
|
||||
return int(n), Platform_Error(err)
|
||||
}
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
|
||||
iovs := wasi.iovec_t(data)
|
||||
n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
|
||||
return int(n), Platform_Error(err)
|
||||
}
|
||||
@(require_results)
|
||||
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
|
||||
oflags: wasi.oflags_t
|
||||
if mode & O_CREATE == O_CREATE {
|
||||
oflags += {.CREATE}
|
||||
}
|
||||
if mode & O_EXCL == O_EXCL {
|
||||
oflags += {.EXCL}
|
||||
}
|
||||
if mode & O_TRUNC == O_TRUNC {
|
||||
oflags += {.TRUNC}
|
||||
}
|
||||
|
||||
rights: wasi.rights_t = {.FD_SEEK, .FD_FILESTAT_GET}
|
||||
switch mode & (O_RDONLY|O_WRONLY|O_RDWR) {
|
||||
case O_RDONLY: rights += {.FD_READ}
|
||||
case O_WRONLY: rights += {.FD_WRITE}
|
||||
case O_RDWR: rights += {.FD_READ, .FD_WRITE}
|
||||
}
|
||||
|
||||
fdflags: wasi.fdflags_t
|
||||
if mode & O_APPEND == O_APPEND {
|
||||
fdflags += {.APPEND}
|
||||
}
|
||||
if mode & O_NONBLOCK == O_NONBLOCK {
|
||||
fdflags += {.NONBLOCK}
|
||||
}
|
||||
if mode & O_SYNC == O_SYNC {
|
||||
fdflags += {.SYNC}
|
||||
}
|
||||
|
||||
dir_fd, relative, ok := wasi_match_preopen(path)
|
||||
if !ok {
|
||||
return INVALID_HANDLE, Errno(wasi.errno_t.BADF)
|
||||
}
|
||||
|
||||
fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags)
|
||||
return Handle(fd), Platform_Error(err)
|
||||
}
|
||||
close :: proc(fd: Handle) -> Errno {
|
||||
err := wasi.fd_close(wasi.fd_t(fd))
|
||||
return Platform_Error(err)
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> Error {
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
|
||||
n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence))
|
||||
return i64(n), Platform_Error(err)
|
||||
}
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return 0
|
||||
}
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
return 1
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
file_size :: proc(fd: Handle) -> (size: i64, err: Errno) {
|
||||
stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return
|
||||
size = i64(stat.size)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
runtime._cleanup_runtime_contextless()
|
||||
wasi.proc_exit(wasi.exitcode_t(code))
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
@@ -1,871 +0,0 @@
|
||||
#+build windows
|
||||
package os_old
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "base:runtime"
|
||||
import "base:intrinsics"
|
||||
import "core:unicode/utf16"
|
||||
|
||||
Handle :: distinct uintptr
|
||||
File_Time :: distinct u64
|
||||
|
||||
INVALID_HANDLE :: ~Handle(0)
|
||||
|
||||
O_RDONLY :: 0x00000
|
||||
O_WRONLY :: 0x00001
|
||||
O_RDWR :: 0x00002
|
||||
O_CREATE :: 0x00040
|
||||
O_EXCL :: 0x00080
|
||||
O_NOCTTY :: 0x00100
|
||||
O_TRUNC :: 0x00200
|
||||
O_NONBLOCK :: 0x00800
|
||||
O_APPEND :: 0x00400
|
||||
O_SYNC :: 0x01000
|
||||
O_ASYNC :: 0x02000
|
||||
O_CLOEXEC :: 0x80000
|
||||
|
||||
_Platform_Error :: win32.System_Error
|
||||
|
||||
ERROR_FILE_NOT_FOUND :: _Platform_Error(2)
|
||||
ERROR_PATH_NOT_FOUND :: _Platform_Error(3)
|
||||
ERROR_ACCESS_DENIED :: _Platform_Error(5)
|
||||
ERROR_INVALID_HANDLE :: _Platform_Error(6)
|
||||
ERROR_NOT_ENOUGH_MEMORY :: _Platform_Error(8)
|
||||
ERROR_NO_MORE_FILES :: _Platform_Error(18)
|
||||
ERROR_HANDLE_EOF :: _Platform_Error(38)
|
||||
ERROR_NETNAME_DELETED :: _Platform_Error(64)
|
||||
ERROR_FILE_EXISTS :: _Platform_Error(80)
|
||||
ERROR_INVALID_PARAMETER :: _Platform_Error(87)
|
||||
ERROR_BROKEN_PIPE :: _Platform_Error(109)
|
||||
ERROR_BUFFER_OVERFLOW :: _Platform_Error(111)
|
||||
ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122)
|
||||
ERROR_MOD_NOT_FOUND :: _Platform_Error(126)
|
||||
ERROR_PROC_NOT_FOUND :: _Platform_Error(127)
|
||||
ERROR_NEGATIVE_SEEK :: _Platform_Error(131)
|
||||
ERROR_DIR_NOT_EMPTY :: _Platform_Error(145)
|
||||
ERROR_ALREADY_EXISTS :: _Platform_Error(183)
|
||||
ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203)
|
||||
ERROR_MORE_DATA :: _Platform_Error(234)
|
||||
ERROR_OPERATION_ABORTED :: _Platform_Error(995)
|
||||
ERROR_IO_PENDING :: _Platform_Error(997)
|
||||
ERROR_NOT_FOUND :: _Platform_Error(1168)
|
||||
ERROR_PRIVILEGE_NOT_HELD :: _Platform_Error(1314)
|
||||
WSAEACCES :: _Platform_Error(10013)
|
||||
WSAECONNRESET :: _Platform_Error(10054)
|
||||
|
||||
ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe
|
||||
ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir
|
||||
|
||||
// "Argv" arguments converted to Odin strings
|
||||
args := _alloc_command_line_arguments()
|
||||
|
||||
@(require_results, no_instrumentation)
|
||||
get_last_error :: proc "contextless" () -> 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_HANDLE_EOF:
|
||||
return .EOF
|
||||
|
||||
case win32.ERROR_INVALID_HANDLE:
|
||||
return .Invalid_File
|
||||
|
||||
case win32.ERROR_NEGATIVE_SEEK:
|
||||
return .Invalid_Offset
|
||||
|
||||
case
|
||||
win32.ERROR_BAD_ARGUMENTS,
|
||||
win32.ERROR_INVALID_PARAMETER,
|
||||
win32.ERROR_NOT_ENOUGH_MEMORY,
|
||||
win32.ERROR_NO_MORE_FILES,
|
||||
win32.ERROR_LOCK_VIOLATION,
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
last_write_time :: proc(fd: Handle) -> (File_Time, Error) {
|
||||
file_info: win32.BY_HANDLE_FILE_INFORMATION
|
||||
if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) {
|
||||
return 0, get_last_error()
|
||||
}
|
||||
lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime)
|
||||
hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime)
|
||||
return lo | hi << 32, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
|
||||
data: win32.WIN32_FILE_ATTRIBUTE_DATA
|
||||
|
||||
wide_path := win32.utf8_to_wstring(name)
|
||||
if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) {
|
||||
return 0, get_last_error()
|
||||
}
|
||||
|
||||
l := File_Time(data.ftLastWriteTime.dwLowDateTime)
|
||||
h := File_Time(data.ftLastWriteTime.dwHighDateTime)
|
||||
return l | h << 32, nil
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
get_page_size :: proc() -> int {
|
||||
// NOTE(tetra): The page size never changes, so why do anything complicated
|
||||
// if we don't have to.
|
||||
@static page_size := -1
|
||||
if page_size != -1 {
|
||||
return page_size
|
||||
}
|
||||
|
||||
info: win32.SYSTEM_INFO
|
||||
win32.GetSystemInfo(&info)
|
||||
page_size = int(info.dwPageSize)
|
||||
return page_size
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
length : win32.DWORD = 0
|
||||
result := win32.GetLogicalProcessorInformation(nil, &length)
|
||||
|
||||
thread_count := 0
|
||||
if !result && win32.GetLastError() == 122 && length > 0 {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
processors := make([]win32.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, length, context.temp_allocator)
|
||||
|
||||
result = win32.GetLogicalProcessorInformation(&processors[0], &length)
|
||||
if result {
|
||||
for processor in processors {
|
||||
if processor.Relationship == .RelationProcessorCore {
|
||||
thread := intrinsics.count_ones(processor.ProcessorMask)
|
||||
thread_count += int(thread)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thread_count
|
||||
}
|
||||
|
||||
exit :: proc "contextless" (code: int) -> ! {
|
||||
runtime._cleanup_runtime_contextless()
|
||||
win32.ExitProcess(win32.DWORD(code))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(require_results)
|
||||
current_thread_id :: proc "contextless" () -> int {
|
||||
return int(win32.GetCurrentThreadId())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
_alloc_command_line_arguments :: proc "contextless" () -> []string {
|
||||
context = runtime.default_context()
|
||||
arg_count: i32
|
||||
arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
|
||||
arg_list := make([]string, int(arg_count))
|
||||
for _, i in arg_list {
|
||||
wc_str := (^win32.wstring)(uintptr(arg_list_ptr) + size_of(win32.wstring)*uintptr(i))^
|
||||
olen := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1,
|
||||
nil, 0, nil, nil)
|
||||
|
||||
buf := make([]byte, int(olen))
|
||||
n := win32.WideCharToMultiByte(win32.CP_UTF8, 0, wc_str, -1,
|
||||
raw_data(buf), olen, nil, nil)
|
||||
if n > 0 {
|
||||
n -= 1
|
||||
}
|
||||
arg_list[i] = string(buf[:n])
|
||||
}
|
||||
|
||||
return arg_list
|
||||
}
|
||||
|
||||
@(private, fini)
|
||||
_delete_command_line_arguments :: proc "contextless" () {
|
||||
context = runtime.default_context()
|
||||
for s in args {
|
||||
delete(s)
|
||||
}
|
||||
delete(args)
|
||||
}
|
||||
|
||||
/*
|
||||
Windows 11 (preview) has the same major and minor version numbers
|
||||
as Windows 10: 10 and 0 respectively.
|
||||
|
||||
To determine if you're on Windows 10 or 11, we need to look at
|
||||
the build number. As far as we can tell right now, the cutoff is build 22_000.
|
||||
|
||||
TODO: Narrow down this range once Win 11 is published and the last Win 10 builds
|
||||
become available.
|
||||
*/
|
||||
WINDOWS_11_BUILD_CUTOFF :: 22_000
|
||||
|
||||
@(require_results)
|
||||
get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW {
|
||||
osvi : win32.OSVERSIONINFOEXW
|
||||
osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW)
|
||||
win32.RtlGetVersion(&osvi)
|
||||
return osvi
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_xp :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_vista :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_7 :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_8 :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_8_1 :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_10 :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < WINDOWS_11_BUILD_CUTOFF)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_windows_11 :: proc "contextless" () -> bool {
|
||||
osvi := get_windows_version_w()
|
||||
return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
return c == '/' || c == '\\'
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) {
|
||||
if len(path) == 0 {
|
||||
return INVALID_HANDLE, General_Error.Not_Exist
|
||||
}
|
||||
|
||||
access: u32
|
||||
switch mode & (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
|
||||
}
|
||||
|
||||
if mode&O_CREATE != 0 {
|
||||
access |= win32.FILE_GENERIC_WRITE
|
||||
}
|
||||
if mode&O_APPEND != 0 {
|
||||
access &~= win32.FILE_GENERIC_WRITE
|
||||
access |= win32.FILE_APPEND_DATA
|
||||
}
|
||||
|
||||
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 mode&O_CLOEXEC == 0 {
|
||||
sa = &sa_inherit
|
||||
}
|
||||
|
||||
create_mode: u32
|
||||
switch {
|
||||
case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL):
|
||||
create_mode = win32.CREATE_NEW
|
||||
case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC):
|
||||
create_mode = win32.CREATE_ALWAYS
|
||||
case mode&O_CREATE == O_CREATE:
|
||||
create_mode = win32.OPEN_ALWAYS
|
||||
case mode&O_TRUNC == O_TRUNC:
|
||||
create_mode = win32.TRUNCATE_EXISTING
|
||||
case:
|
||||
create_mode = win32.OPEN_EXISTING
|
||||
}
|
||||
|
||||
attrs := win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
|
||||
if mode & (O_NONBLOCK) == O_NONBLOCK {
|
||||
attrs |= win32.FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
|
||||
wide_path := win32.utf8_to_wstring(path)
|
||||
handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, attrs, nil))
|
||||
if handle != INVALID_HANDLE {
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
return INVALID_HANDLE, get_last_error()
|
||||
}
|
||||
|
||||
close :: proc(fd: Handle) -> Error {
|
||||
if !win32.CloseHandle(win32.HANDLE(fd)) {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
flush :: proc(fd: Handle) -> (err: Error) {
|
||||
if !win32.FlushFileBuffers(win32.HANDLE(fd)) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
write :: proc(fd: Handle, data: []byte) -> (int, Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
single_write_length: win32.DWORD
|
||||
total_write: i64
|
||||
length := i64(len(data))
|
||||
|
||||
for total_write < length {
|
||||
remaining := length - total_write
|
||||
to_write := win32.DWORD(min(i32(remaining), MAX_RW))
|
||||
|
||||
e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil)
|
||||
if single_write_length <= 0 || !e {
|
||||
return int(total_write), get_last_error()
|
||||
}
|
||||
total_write += i64(single_write_length)
|
||||
}
|
||||
return int(total_write), nil
|
||||
}
|
||||
|
||||
@(private="file", require_results)
|
||||
read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
BUF_SIZE :: 386
|
||||
buf16: [BUF_SIZE]u16
|
||||
buf8: [4*BUF_SIZE]u8
|
||||
|
||||
for n < len(b) && err == nil {
|
||||
min_read := max(len(b)/4, 1 if len(b) > 0 else 0)
|
||||
max_read := u32(min(BUF_SIZE, min_read))
|
||||
if max_read == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
single_read_length: u32
|
||||
ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)
|
||||
if !ok {
|
||||
err = get_last_error()
|
||||
}
|
||||
|
||||
buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length])
|
||||
src := buf8[:buf8_len]
|
||||
|
||||
ctrl_z := false
|
||||
for i := 0; i < len(src) && n < len(b); i += 1 {
|
||||
x := src[i]
|
||||
if x == 0x1a { // ctrl-z
|
||||
ctrl_z = true
|
||||
break
|
||||
}
|
||||
b[n] = x
|
||||
n += 1
|
||||
}
|
||||
if ctrl_z || single_read_length < max_read {
|
||||
break
|
||||
}
|
||||
|
||||
// NOTE(bill): if the last two values were a newline, then it is expected that
|
||||
// this is the end of the input
|
||||
if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
handle := win32.HANDLE(fd)
|
||||
|
||||
m: u32
|
||||
is_console := win32.GetConsoleMode(handle, &m)
|
||||
length := len(data)
|
||||
|
||||
// NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
|
||||
to_read := min(i64(length), MAX_RW)
|
||||
|
||||
if is_console {
|
||||
total_read, err = read_console(handle, data[total_read:][:to_read])
|
||||
if err != nil {
|
||||
return total_read, err
|
||||
}
|
||||
} else {
|
||||
// NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
|
||||
bytes_read: win32.DWORD
|
||||
if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e {
|
||||
// Successful read can mean two things, including EOF, see:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file
|
||||
if bytes_read == 0 {
|
||||
return 0, .EOF
|
||||
} else {
|
||||
return int(bytes_read), nil
|
||||
}
|
||||
} else {
|
||||
return 0, get_last_error()
|
||||
}
|
||||
}
|
||||
return total_read, nil
|
||||
}
|
||||
|
||||
seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
|
||||
w: u32
|
||||
switch whence {
|
||||
case 0: w = win32.FILE_BEGIN
|
||||
case 1: w = win32.FILE_CURRENT
|
||||
case 2: w = win32.FILE_END
|
||||
case:
|
||||
return 0, .Invalid_Whence
|
||||
}
|
||||
hi := i32(offset>>32)
|
||||
lo := i32(offset)
|
||||
ft := win32.GetFileType(win32.HANDLE(fd))
|
||||
if ft == win32.FILE_TYPE_PIPE {
|
||||
return 0, .File_Is_Pipe
|
||||
}
|
||||
|
||||
dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w)
|
||||
if dw_ptr == win32.INVALID_SET_FILE_POINTER {
|
||||
err := get_last_error()
|
||||
return 0, err
|
||||
}
|
||||
return i64(hi)<<32 + i64(dw_ptr), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
file_size :: proc(fd: Handle) -> (i64, Error) {
|
||||
length: win32.LARGE_INTEGER
|
||||
err: Error
|
||||
if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return i64(length), err
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
MAX_RW :: 1<<30
|
||||
|
||||
@(private)
|
||||
pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
curr_off := seek(fd, 0, 1) or_return
|
||||
defer seek(fd, curr_off, 0)
|
||||
|
||||
buf := data
|
||||
if len(buf) > MAX_RW {
|
||||
buf = buf[:MAX_RW]
|
||||
}
|
||||
|
||||
o := win32.OVERLAPPED{
|
||||
OffsetHigh = u32(offset>>32),
|
||||
Offset = u32(offset),
|
||||
}
|
||||
|
||||
// TODO(bill): Determine the correct behaviour for consoles
|
||||
|
||||
h := win32.HANDLE(fd)
|
||||
done: win32.DWORD
|
||||
e: Error
|
||||
if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
|
||||
e = get_last_error()
|
||||
done = 0
|
||||
}
|
||||
return int(done), e
|
||||
}
|
||||
@(private)
|
||||
pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
curr_off := seek(fd, 0, 1) or_return
|
||||
defer seek(fd, curr_off, 0)
|
||||
|
||||
buf := data
|
||||
if len(buf) > MAX_RW {
|
||||
buf = buf[:MAX_RW]
|
||||
}
|
||||
|
||||
o := win32.OVERLAPPED{
|
||||
OffsetHigh = u32(offset>>32),
|
||||
Offset = u32(offset),
|
||||
}
|
||||
|
||||
h := win32.HANDLE(fd)
|
||||
done: win32.DWORD
|
||||
e: Error
|
||||
if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) {
|
||||
e = get_last_error()
|
||||
done = 0
|
||||
}
|
||||
return int(done), e
|
||||
}
|
||||
|
||||
/*
|
||||
read_at returns n: 0, err: 0 on EOF
|
||||
*/
|
||||
read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if offset < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
|
||||
b, offset := data, offset
|
||||
for len(b) > 0 {
|
||||
m, e := pread(fd, b, offset)
|
||||
if e == ERROR_EOF {
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
if e != nil {
|
||||
err = e
|
||||
break
|
||||
}
|
||||
n += m
|
||||
b = b[m:]
|
||||
offset += i64(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) {
|
||||
if offset < 0 {
|
||||
return 0, .Invalid_Offset
|
||||
}
|
||||
|
||||
b, offset := data, offset
|
||||
for len(b) > 0 {
|
||||
m := pwrite(fd, b, offset) or_return
|
||||
n += m
|
||||
b = b[m:]
|
||||
offset += i64(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
// NOTE(bill): Uses startup to initialize it
|
||||
stdin := get_std_handle(uint(win32.STD_INPUT_HANDLE))
|
||||
stdout := get_std_handle(uint(win32.STD_OUTPUT_HANDLE))
|
||||
stderr := get_std_handle(uint(win32.STD_ERROR_HANDLE))
|
||||
|
||||
|
||||
@(require_results)
|
||||
get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
||||
fd := win32.GetStdHandle(win32.DWORD(h))
|
||||
return Handle(fd)
|
||||
}
|
||||
|
||||
|
||||
exists :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
return attribs != win32.INVALID_FILE_ATTRIBUTES
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_file :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
if attribs != win32.INVALID_FILE_ATTRIBUTES {
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
is_dir :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
if attribs != win32.INVALID_FILE_ATTRIBUTES {
|
||||
return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName
|
||||
@private cwd_lock := win32.SRWLOCK{} // zero is initialized
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
|
||||
dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
|
||||
|
||||
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
|
||||
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
|
||||
|
||||
win32.ReleaseSRWLockExclusive(&cwd_lock)
|
||||
|
||||
return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else ""
|
||||
}
|
||||
|
||||
set_current_directory :: proc(path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wstr := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||
|
||||
if !win32.SetCurrentDirectoryW(wstr) {
|
||||
err = get_last_error()
|
||||
}
|
||||
|
||||
win32.ReleaseSRWLockExclusive(&cwd_lock)
|
||||
|
||||
return
|
||||
}
|
||||
change_directory :: set_current_directory
|
||||
|
||||
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
// Mode is unused on Windows, but is needed on *nix
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.CreateDirectoryW(wpath, nil) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
remove_directory :: proc(path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.RemoveDirectoryW(wpath) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
is_abs :: proc(path: string) -> bool {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
return true
|
||||
}
|
||||
when ODIN_OS == .Windows {
|
||||
if len(path) > 2 {
|
||||
switch path[0] {
|
||||
case 'A'..='Z', 'a'..='z':
|
||||
return path[1] == ':' && is_path_separator(path[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
fix_long_path :: proc(path: string) -> string {
|
||||
if len(path) < 248 {
|
||||
return path
|
||||
}
|
||||
|
||||
if len(path) >= 2 && path[:2] == `\\` {
|
||||
return path
|
||||
}
|
||||
if !is_abs(path) {
|
||||
return path
|
||||
}
|
||||
|
||||
prefix :: `\\?`
|
||||
|
||||
path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator)
|
||||
copy(path_buf, prefix)
|
||||
n := len(path)
|
||||
r, w := 0, len(prefix)
|
||||
for r < n {
|
||||
switch {
|
||||
case is_path_separator(path[r]):
|
||||
r += 1
|
||||
case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])):
|
||||
r += 1
|
||||
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])):
|
||||
return path
|
||||
case:
|
||||
path_buf[w] = '\\'
|
||||
w += 1
|
||||
for ; r < n && !is_path_separator(path[r]); r += 1 {
|
||||
path_buf[w] = path[r]
|
||||
w += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w == len(`\\?\c:`) {
|
||||
path_buf[w] = '\\'
|
||||
w += 1
|
||||
}
|
||||
return string(path_buf[:w])
|
||||
}
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
n := win32.utf8_to_wstring(fix_long_path(new_name))
|
||||
o := win32.utf8_to_wstring(fix_long_path(old_name))
|
||||
return Platform_Error(win32.CreateHardLinkW(n, o, nil))
|
||||
}
|
||||
|
||||
unlink :: proc(path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.DeleteFileW(wpath) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> (err: Error) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
|
||||
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
|
||||
|
||||
if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) {
|
||||
curr_off := seek(fd, 0, 1) or_return
|
||||
defer seek(fd, curr_off, 0)
|
||||
_= seek(fd, length, 0) or_return
|
||||
ok := win32.SetEndOfFile(win32.HANDLE(fd))
|
||||
if !ok {
|
||||
return get_last_error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
truncate :: proc(path: string, length: i64) -> (err: Error) {
|
||||
fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return
|
||||
defer close(fd)
|
||||
return ftruncate(fd, length)
|
||||
}
|
||||
|
||||
|
||||
remove :: proc(name: string) -> Error {
|
||||
p := win32.utf8_to_wstring(fix_long_path(name))
|
||||
err, err1: win32.DWORD
|
||||
if !win32.DeleteFileW(p) {
|
||||
err = win32.GetLastError()
|
||||
}
|
||||
if err == 0 {
|
||||
return nil
|
||||
}
|
||||
if !win32.RemoveDirectoryW(p) {
|
||||
err1 = win32.GetLastError()
|
||||
}
|
||||
if err1 == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != err1 {
|
||||
a := win32.GetFileAttributesW(p)
|
||||
if a == ~u32(0) {
|
||||
err = win32.GetLastError()
|
||||
} else {
|
||||
if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
err = err1
|
||||
} else if a & win32.FILE_ATTRIBUTE_READONLY != 0 {
|
||||
if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) {
|
||||
err = 0
|
||||
if !win32.DeleteFileW(p) {
|
||||
err = win32.GetLastError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Platform_Error(err)
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
pipe :: proc() -> (r, w: Handle, err: Error) {
|
||||
sa: win32.SECURITY_ATTRIBUTES
|
||||
sa.nLength = size_of(win32.SECURITY_ATTRIBUTES)
|
||||
sa.bInheritHandle = true
|
||||
if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) {
|
||||
err = get_last_error()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import "core:time"
|
||||
|
||||
File_Info :: struct {
|
||||
fullpath: string, // allocated
|
||||
name: string, // uses `fullpath` as underlying data
|
||||
size: i64,
|
||||
mode: File_Mode,
|
||||
is_dir: bool,
|
||||
creation_time: time.Time,
|
||||
modification_time: time.Time,
|
||||
access_time: time.Time,
|
||||
}
|
||||
|
||||
file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) {
|
||||
for i := len(infos)-1; i >= 0; i -= 1 {
|
||||
file_info_delete(infos[i], allocator)
|
||||
}
|
||||
delete(infos, allocator)
|
||||
}
|
||||
|
||||
file_info_delete :: proc(fi: File_Info, allocator := context.allocator) {
|
||||
delete(fi.fullpath, allocator)
|
||||
}
|
||||
|
||||
File_Mode :: distinct u32
|
||||
|
||||
File_Mode_Dir :: File_Mode(1<<16)
|
||||
File_Mode_Named_Pipe :: File_Mode(1<<17)
|
||||
File_Mode_Device :: File_Mode(1<<18)
|
||||
File_Mode_Char_Device :: File_Mode(1<<19)
|
||||
File_Mode_Sym_Link :: File_Mode(1<<20)
|
||||
@@ -1,134 +0,0 @@
|
||||
#+build linux, darwin, freebsd, openbsd, netbsd
|
||||
package os_old
|
||||
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
For reference
|
||||
-------------
|
||||
|
||||
Unix_File_Time :: struct {
|
||||
seconds: i64,
|
||||
nanoseconds: i64,
|
||||
}
|
||||
|
||||
Stat :: struct {
|
||||
device_id: u64, // ID of device containing file
|
||||
serial: u64, // File serial number
|
||||
nlink: u64, // Number of hard links
|
||||
mode: u32, // Mode of the file
|
||||
uid: u32, // User ID of the file's owner
|
||||
gid: u32, // Group ID of the file's group
|
||||
_padding: i32, // 32 bits of padding
|
||||
rdev: u64, // Device ID, if device
|
||||
size: i64, // Size of the file, in bytes
|
||||
block_size: i64, // Optimal bllocksize for I/O
|
||||
blocks: i64, // Number of 512-byte blocks allocated
|
||||
|
||||
last_access: Unix_File_Time, // Time of last access
|
||||
modified: Unix_File_Time, // Time of last modification
|
||||
status_change: Unix_File_Time, // Time of last status change
|
||||
|
||||
_reserve1,
|
||||
_reserve2,
|
||||
_reserve3: i64,
|
||||
};
|
||||
|
||||
Time :: struct {
|
||||
_nsec: i64, // zero is 1970-01-01 00:00:00
|
||||
}
|
||||
|
||||
File_Info :: struct {
|
||||
fullpath: string,
|
||||
name: string,
|
||||
size: i64,
|
||||
mode: File_Mode,
|
||||
is_dir: bool,
|
||||
creation_time: time.Time,
|
||||
modification_time: time.Time,
|
||||
access_time: time.Time,
|
||||
}
|
||||
*/
|
||||
|
||||
@(private, require_results)
|
||||
_make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time {
|
||||
return time.Time{
|
||||
_nsec = i64(uft.nanoseconds) + i64(uft.seconds) * 1_000_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) {
|
||||
fi.size = s.size
|
||||
fi.mode = cast(File_Mode)s.mode
|
||||
fi.is_dir = S_ISDIR(s.mode)
|
||||
|
||||
// NOTE(laleksic, 2021-01-21): Not really creation time, but closest we can get (maybe better to leave it 0?)
|
||||
fi.creation_time = _make_time_from_unix_file_time(s.status_change)
|
||||
|
||||
fi.modification_time = _make_time_from_unix_file_time(s.modified)
|
||||
fi.access_time = _make_time_from_unix_file_time(s.last_access)
|
||||
}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
path_base :: proc(path: string) -> string {
|
||||
is_separator :: proc(c: byte) -> bool {
|
||||
return c == '/'
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return "."
|
||||
}
|
||||
|
||||
path := path
|
||||
for len(path) > 0 && is_separator(path[len(path)-1]) {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
|
||||
i := len(path)-1
|
||||
for i >= 0 && !is_separator(path[i]) {
|
||||
i -= 1
|
||||
}
|
||||
if i >= 0 {
|
||||
path = path[i+1:]
|
||||
}
|
||||
if path == "" {
|
||||
return "/"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
s := _lstat(name) or_return
|
||||
_fill_file_info_from_stat(&fi, s)
|
||||
fi.fullpath = absolute_path_from_relative(name) or_return
|
||||
fi.name = path_base(fi.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
s := _stat(name) or_return
|
||||
_fill_file_info_from_stat(&fi, s)
|
||||
fi.fullpath = absolute_path_from_relative(name) or_return
|
||||
fi.name = path_base(fi.fullpath)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
s := _fstat(fd) or_return
|
||||
_fill_file_info_from_stat(&fi, s)
|
||||
fi.fullpath = absolute_path_from_handle(fd) or_return
|
||||
fi.name = path_base(fi.fullpath)
|
||||
return
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import "core:time"
|
||||
import "base:runtime"
|
||||
import win32 "core:sys/windows"
|
||||
|
||||
@(private, require_results)
|
||||
full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) {
|
||||
context.allocator = allocator
|
||||
|
||||
name := name
|
||||
if name == "" {
|
||||
name = "."
|
||||
}
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
p := win32.utf8_to_utf16(name, context.temp_allocator)
|
||||
buf := make([dynamic]u16, 100)
|
||||
defer delete(buf)
|
||||
for {
|
||||
n := win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil)
|
||||
if n == 0 {
|
||||
return "", get_last_error()
|
||||
}
|
||||
if n <= u32(len(buf)) {
|
||||
return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil
|
||||
}
|
||||
resize(&buf, len(buf)*2)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) {
|
||||
if len(name) == 0 {
|
||||
return {}, ERROR_PATH_NOT_FOUND
|
||||
}
|
||||
|
||||
context.allocator = allocator
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
wname := win32.utf8_to_wstring(fix_long_path(name), context.temp_allocator)
|
||||
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
|
||||
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
|
||||
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||
// Not a symlink
|
||||
return file_info_from_win32_file_attribute_data(&fa, name)
|
||||
}
|
||||
|
||||
err := 0 if ok else win32.GetLastError()
|
||||
|
||||
if err == win32.ERROR_SHARING_VIOLATION {
|
||||
fd: win32.WIN32_FIND_DATAW
|
||||
sh := win32.FindFirstFileW(wname, &fd)
|
||||
if sh == win32.INVALID_HANDLE_VALUE {
|
||||
e = get_last_error()
|
||||
return
|
||||
}
|
||||
win32.FindClose(sh)
|
||||
|
||||
return file_info_from_win32_find_data(&fd, name)
|
||||
}
|
||||
|
||||
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
|
||||
if h == win32.INVALID_HANDLE_VALUE {
|
||||
e = get_last_error()
|
||||
return
|
||||
}
|
||||
defer win32.CloseHandle(h)
|
||||
return file_info_from_get_file_information_by_handle(name, h)
|
||||
}
|
||||
|
||||
|
||||
@(require_results)
|
||||
lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
|
||||
attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
|
||||
attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT
|
||||
return _stat(name, attrs, allocator)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) {
|
||||
attrs := win32.FILE_FLAG_BACKUP_SEMANTICS
|
||||
return _stat(name, attrs, allocator)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) {
|
||||
if fd == 0 {
|
||||
err = ERROR_INVALID_HANDLE
|
||||
}
|
||||
context.allocator = allocator
|
||||
|
||||
path := cleanpath_from_handle(fd) or_return
|
||||
defer if err != nil {
|
||||
delete(path)
|
||||
}
|
||||
|
||||
h := win32.HANDLE(fd)
|
||||
switch win32.GetFileType(h) {
|
||||
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
|
||||
fi.name = basename(path)
|
||||
fi.mode |= file_type_mode(h)
|
||||
err = nil
|
||||
case:
|
||||
fi = file_info_from_get_file_information_by_handle(path, h) or_return
|
||||
}
|
||||
fi.fullpath = path
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
|
||||
buf := buf
|
||||
N := 0
|
||||
for c, i in buf {
|
||||
if c == 0 { break }
|
||||
N = i+1
|
||||
}
|
||||
buf = buf[:N]
|
||||
|
||||
if len(buf) >= 4 && buf[0] == '\\' && buf[1] == '\\' && buf[2] == '?' && buf[3] == '\\' {
|
||||
buf = buf[4:]
|
||||
|
||||
/*
|
||||
NOTE(Jeroen): Properly handle UNC paths.
|
||||
We need to turn `\\?\UNC\synology.local` into `\\synology.local`.
|
||||
*/
|
||||
if len(buf) >= 3 && buf[0] == 'U' && buf[1] == 'N' && buf[2] == 'C' {
|
||||
buf = buf[2:]
|
||||
buf[0] = '\\'
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return
|
||||
return win32.utf16_to_utf8(buf, context.allocator)
|
||||
}
|
||||
@(private, require_results)
|
||||
cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) {
|
||||
if fd == 0 {
|
||||
return nil, ERROR_INVALID_HANDLE
|
||||
}
|
||||
h := win32.HANDLE(fd)
|
||||
|
||||
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
|
||||
if n == 0 {
|
||||
return nil, get_last_error()
|
||||
}
|
||||
buf := make([]u16, max(n, win32.DWORD(260))+1, allocator)
|
||||
buf_len := win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), n, 0)
|
||||
return buf[:buf_len], nil
|
||||
}
|
||||
@(private, require_results)
|
||||
cleanpath_from_buf :: proc(buf: []u16) -> string {
|
||||
buf := buf
|
||||
buf = cleanpath_strip_prefix(buf)
|
||||
return win32.utf16_to_utf8(buf, context.allocator) or_else ""
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
basename :: proc(name: string) -> (base: string) {
|
||||
name := name
|
||||
if len(name) > 3 && name[:3] == `\\?` {
|
||||
name = name[3:]
|
||||
}
|
||||
|
||||
if len(name) == 2 && name[1] == ':' {
|
||||
return "."
|
||||
} else if len(name) > 2 && name[1] == ':' {
|
||||
name = name[2:]
|
||||
}
|
||||
i := len(name)-1
|
||||
|
||||
for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 {
|
||||
name = name[:i]
|
||||
}
|
||||
for i -= 1; i >= 0; i -= 1 {
|
||||
if name[i] == '/' || name[i] == '\\' {
|
||||
name = name[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
|
||||
switch win32.GetFileType(h) {
|
||||
case win32.FILE_TYPE_PIPE:
|
||||
return File_Mode_Named_Pipe
|
||||
case win32.FILE_TYPE_CHAR:
|
||||
return File_Mode_Device | File_Mode_Char_Device
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) {
|
||||
if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
|
||||
mode |= 0o444
|
||||
} else {
|
||||
mode |= 0o666
|
||||
}
|
||||
|
||||
is_sym := false
|
||||
if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||
is_sym = false
|
||||
} else {
|
||||
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
|
||||
}
|
||||
|
||||
if is_sym {
|
||||
mode |= File_Mode_Sym_Link
|
||||
} else {
|
||||
if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
mode |= 0o111 | File_Mode_Dir
|
||||
}
|
||||
|
||||
if h != nil {
|
||||
mode |= file_type_mode(h)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) {
|
||||
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
|
||||
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
|
||||
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) {
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
|
||||
|
||||
fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0
|
||||
|
||||
windows_set_file_info_times(&fi, d)
|
||||
|
||||
fi.fullpath, e = full_path_from_name(name)
|
||||
fi.name = basename(fi.fullpath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) {
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
|
||||
|
||||
fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0
|
||||
|
||||
windows_set_file_info_times(&fi, d)
|
||||
|
||||
fi.fullpath, e = full_path_from_name(name)
|
||||
fi.name = basename(fi.fullpath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private, require_results)
|
||||
file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) {
|
||||
d: win32.BY_HANDLE_FILE_INFORMATION
|
||||
if !win32.GetFileInformationByHandle(h, &d) {
|
||||
err := get_last_error()
|
||||
return {}, err
|
||||
|
||||
}
|
||||
|
||||
ti: win32.FILE_ATTRIBUTE_TAG_INFO
|
||||
if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
|
||||
err := get_last_error()
|
||||
if err != ERROR_INVALID_PARAMETER {
|
||||
return {}, err
|
||||
}
|
||||
// Indicate this is a symlink on FAT file systems
|
||||
ti.ReparseTag = 0
|
||||
}
|
||||
|
||||
fi: File_Info
|
||||
|
||||
fi.fullpath = path
|
||||
fi.name = basename(path)
|
||||
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
|
||||
|
||||
fi.mode |= file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag)
|
||||
fi.is_dir = fi.mode & File_Mode_Dir != 0
|
||||
|
||||
windows_set_file_info_times(&fi, &d)
|
||||
|
||||
return fi, nil
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package os_old
|
||||
|
||||
import "core:io"
|
||||
|
||||
stream_from_handle :: proc(fd: Handle) -> io.Stream {
|
||||
s: io.Stream
|
||||
s.data = rawptr(uintptr(fd))
|
||||
s.procedure = _file_stream_proc
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
@(private)
|
||||
_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
|
||||
fd := Handle(uintptr(stream_data))
|
||||
n_int: int
|
||||
os_err: Error
|
||||
switch mode {
|
||||
case .Close:
|
||||
os_err = close(fd)
|
||||
case .Flush:
|
||||
os_err = flush(fd)
|
||||
case .Read:
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n_int, os_err = read(fd, p)
|
||||
n = i64(n_int)
|
||||
if n == 0 && os_err == nil {
|
||||
err = .EOF
|
||||
}
|
||||
|
||||
case .Read_At:
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n_int, os_err = read_at(fd, p, offset)
|
||||
n = i64(n_int)
|
||||
if n == 0 && os_err == nil {
|
||||
err = .EOF
|
||||
}
|
||||
case .Write:
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n_int, os_err = write(fd, p)
|
||||
n = i64(n_int)
|
||||
if n == 0 && os_err == nil {
|
||||
err = .EOF
|
||||
}
|
||||
case .Write_At:
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n_int, os_err = write_at(fd, p, offset)
|
||||
n = i64(n_int)
|
||||
if n == 0 && os_err == nil {
|
||||
err = .EOF
|
||||
}
|
||||
case .Seek:
|
||||
n, os_err = seek(fd, offset, int(whence))
|
||||
case .Size:
|
||||
n, os_err = file_size(fd)
|
||||
case .Destroy:
|
||||
err = .Unsupported
|
||||
case .Query:
|
||||
return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query})
|
||||
}
|
||||
|
||||
if err == nil && os_err != nil {
|
||||
err = error_to_io_error(os_err)
|
||||
}
|
||||
if err != nil {
|
||||
n = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user