Deprecate core:os/old as advertised.

This commit is contained in:
Jeroen van Rijn
2026-08-23 21:42:58 +02:00
parent 965970af71
commit ce379d033a
19 changed files with 0 additions and 8391 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -1,2 +0,0 @@
// The original implementation of `core:os`, to be removed in Q2 2026.
package os_old

View File

@@ -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
}
}
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -1,4 +0,0 @@
#+build freestanding
package os_old
#panic("package os_old does not support a freestanding target")

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}