core:os -> core:os/old && core:os/os2 -> core:os

This commit is contained in:
Jeroen van Rijn
2026-02-09 15:50:21 +01:00
parent 8ed264680b
commit e7dbabf668
169 changed files with 1716 additions and 1718 deletions

View File

@@ -2,11 +2,11 @@
A small `GZIP` unpacker.
Example:
import "core:bytes"
import os "core:os/os2"
import "core:compress"
import "core:compress/gzip"
import "core:fmt"
import "core:bytes"
import "core:os"
import "core:compress"
import "core:compress/gzip"
import "core:fmt"
// Small GZIP file with fextra, fname and fcomment present.
@private

View File

@@ -14,12 +14,12 @@ package compress_gzip
to be the input to a complementary TAR implementation.
*/
import "core:compress/zlib"
import "core:compress"
import os "core:os/os2"
import "core:io"
import "core:bytes"
import "core:hash"
import "core:compress/zlib"
import "core:compress"
import "core:os"
import "core:io"
import "core:bytes"
import "core:hash"
Magic :: enum u16le {
GZIP = 0x8b << 8 | 0x1f,

View File

@@ -3,7 +3,7 @@
package crypto_hash
import "core:io"
import os "core:os/os2"
import "core:os"
// `hash_file` will read the file provided by the given handle and return the
// computed digest in a newly allocated slice.

View File

@@ -6,7 +6,7 @@ Example:
import "core:fmt"
import "core:encoding/csv"
import os "core:os/os2"
import "core:os"
// Requires keeping the entire CSV file in memory at once
iterate_csv_from_string :: proc(filename: string) {

View File

@@ -2,7 +2,7 @@
#+build !js
package encoding_hxa
import os "core:os/os2"
import "core:os"
read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
context.allocator = allocator

View File

@@ -3,7 +3,7 @@
package encoding_ini
import "base:runtime"
import os "core:os/os2"
import "core:os"
load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) {
data, data_err := os.read_entire_file(path, allocator)

View File

@@ -2,7 +2,7 @@
#+build !js
package encoding_xml
import os "core:os/os2"
import "core:os"
// Load an XML file
load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) {

View File

@@ -2,7 +2,7 @@ package flags
import "base:runtime"
import "core:net"
import os "core:os/os2"
import "core:os"
Parse_Error_Reason :: enum {
None,

View File

@@ -4,7 +4,7 @@ import "base:runtime"
import "core:flags"
import "core:fmt"
import "core:net"
import os "core:os/os2"
import "core:os"
import "core:time/datetime"

View File

@@ -1,18 +1,18 @@
#+private
package flags
import "base:intrinsics"
import "base:runtime"
import "core:fmt"
import "core:mem"
import "core:net"
@(require) import os "core:os/os2"
import "core:reflect"
import "core:strconv"
import "core:strings"
@require import "core:time"
@require import "core:time/datetime"
import "core:unicode/utf8"
import "base:intrinsics"
import "base:runtime"
import "core:fmt"
import "core:mem"
import "core:net"
@(require) import "core:os"
import "core:reflect"
import "core:strconv"
import "core:strings"
@(require) import "core:time"
@(require) import "core:time/datetime"
import "core:unicode/utf8"
@(optimization_mode="favor_size")
parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: ^runtime.Type_Info) -> bool {

View File

@@ -1,14 +1,14 @@
#+private
package flags
@require import "base:runtime"
@require import "core:container/bit_array"
@require import "core:fmt"
@require import "core:mem"
@require import os "core:os/os2"
@require import "core:reflect"
@require import "core:strconv"
@require import "core:strings"
@require import "base:runtime"
@require import "core:container/bit_array"
@require import "core:fmt"
@require import "core:mem"
@require import "core:os"
@require import "core:reflect"
@require import "core:strconv"
@require import "core:strings"
// This proc is used to assert that `T` meets the expectations of the library.
@(optimization_mode="favor_size", disabled=ODIN_DISABLE_ASSERT)

View File

@@ -1,9 +1,9 @@
package flags
import "core:fmt"
@require import os "core:os/os2"
import "core:fmt"
@require import "core:os"
@require import "core:path/filepath"
import "core:strings"
import "core:strings"
/*
Parse any arguments into an annotated struct or exit if there was an error.

View File

@@ -3,10 +3,10 @@
#+build !orca
package fmt
import "base:runtime"
import os "core:os/os2"
import "core:io"
import "core:bufio"
import "base:runtime"
import "core:os"
import "core:io"
import "core:bufio"
// NOTE(Jeroen): The other option is to deprecate `fprint*` and make it an alias for `wprint*`, using File.stream directly.

View File

@@ -1,8 +1,8 @@
#+build !js
package core_image_bmp
import os "core:os/os2"
import "core:bytes"
import "core:os"
import "core:bytes"
load :: proc{load_from_file, load_from_bytes, load_from_context}

View File

@@ -1,7 +1,7 @@
#+build !js
package image
import os "core:os/os2"
import "core:os"
load :: proc{
load_from_bytes,

View File

@@ -1,7 +1,7 @@
#+build !js
package jpeg
import os "core:os/os2"
import "core:os"
load :: proc{load_from_file, load_from_bytes, load_from_context}

View File

@@ -1,7 +1,7 @@
#+build !js
package netpbm
import os "core:os/os2"
import "core:os"
load :: proc {
load_from_file,

View File

@@ -1,7 +1,7 @@
#+build !js
package png
import os "core:os/os2"
import "core:os"
load :: proc{load_from_file, load_from_bytes, load_from_context}

View File

@@ -1,8 +1,8 @@
#+build !js
package qoi
import os "core:os/os2"
import "core:bytes"
import "core:os"
import "core:bytes"
load :: proc{load_from_file, load_from_bytes, load_from_context}

View File

@@ -1,8 +1,8 @@
#+build !js
package tga
import os "core:os/os2"
import "core:bytes"
import "core:os"
import "core:bytes"
load :: proc{load_from_file, load_from_bytes, load_from_context}

View File

@@ -3,13 +3,13 @@
#+build !js
package log
import "base:runtime"
import "core:fmt"
import "core:strings"
import os "core:os/os2"
import "core:terminal"
import "core:terminal/ansi"
import "core:time"
import "base:runtime"
import "core:fmt"
import "core:strings"
import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time"
Level_Headers := [?]string{
0..<10 = "[DEBUG] --- ",

View File

@@ -18,7 +18,7 @@ package math_big
*/
import "core:mem"
import os "core:os/os2"
import "core:os"
/*
We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions.

View File

@@ -5,8 +5,8 @@ virtual.Arena usage
Example:
// Source: https://github.com/odin-lang/examples/blob/master/arena_allocator/arena_allocator.odin
import "core:fmt"
import os "core:os/os2"
import "core:fmt"
import "core:os"
// virtual package implements a multi-purpose arena allocator. If you are on a
// platform that does not support virtual memory, then there is also a similar

View File

@@ -2,7 +2,7 @@
#+build !js
package mem_virtual
import os "core:os/os2"
import "core:os"
map_file :: proc{
map_file_from_path,

View File

@@ -2,7 +2,7 @@
#+private
package net
import os "core:os/os2"
import "core:os"
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
context.allocator = allocator

View File

@@ -1,12 +1,12 @@
package odin_parser
import "core:odin/tokenizer"
import "core:odin/ast"
import "core:path/filepath"
import "core:fmt"
import os "core:os/os2"
import "core:slice"
import "core:strings"
import "core:odin/tokenizer"
import "core:odin/ast"
import "core:path/filepath"
import "core:fmt"
import "core:os"
import "core:slice"
import "core:strings"
collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
NO_POS :: tokenizer.Pos{}

View File

@@ -160,8 +160,8 @@ extend its lifetime.
Example:
package main
import "core:fmt"
import os "core:os/os2"
import "core:fmt"
import "core:os"
main :: proc() {
f, oerr := os.open("core")

View File

@@ -136,9 +136,9 @@ If an error occurred opening a directory, you may get zero'd info struct and
Example:
package main
import "core:fmt"
import "core:strings"
import os "core:os/os2"
import "core:fmt"
import "core:strings"
import "core:os"
main :: proc() {
w := os.walker_create("core")

View File

@@ -1,114 +1,144 @@
package os
#+private
package os2
import win32 "core:sys/windows"
import "core:strings"
import "base:runtime"
import "core:time"
import win32 "core:sys/windows"
@(private="file")
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
// Ignore "." and ".."
if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
return
}
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0)
defer win32.CloseHandle(handle)
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0)
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))
if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) {
#assert(size_of(fi.inode) == size_of(file_id_info.FileId))
#assert(size_of(fi.inode) == 16)
runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
}
return
}
Read_Directory_Iterator_Impl :: struct {
find_data: win32.WIN32_FIND_DATAW,
find_handle: win32.HANDLE,
path: string,
prev_fi: File_Info,
no_more_files: bool,
}
@(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 {
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
for !it.impl.no_more_files {
err: Error
file_info_delete(it.impl.prev_fi, file_allocator())
it.impl.prev_fi = {}
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
if err != nil {
read_directory_iterator_set_error(it, it.impl.path, err)
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
if fi.name != "" {
it.impl.prev_fi = fi
ok = true
index = it.index
it.index += 1
}
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
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
e := _get_platform_error()
if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) {
read_directory_iterator_set_error(it, it.impl.path, e)
}
// fi.mode |= file_type_mode(h);
it.impl.no_more_files = true
}
if ok {
return
}
}
return
}
windows_set_file_info_times(&fi, d)
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
it.impl.no_more_files = false
fi.is_dir = fi.mode & File_Mode_Dir != 0
if f == nil || f.impl == nil {
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
if fd == 0 {
return nil, ERROR_INVALID_HANDLE
it.f = f
impl := (^File_Impl)(f.impl)
// NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx.
if it.impl.find_handle != nil {
win32.FindClose(it.impl.find_handle)
}
if it.impl.path != "" {
delete(it.impl.path, file_allocator())
}
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 {
if !is_directory(impl.name) {
read_directory_iterator_set_error(it, impl.name, .Invalid_Dir)
return
}
dfi := make([dynamic]File_Info, 0, size) or_return
wpath := string16(impl.wname)
temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return
wpath_search := make([]u16, len(wpath)+3, temp_allocator)
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
it.impl.find_handle = win32.FindFirstFileW(cstring16(raw_data(wpath_search)), &it.impl.find_data)
if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
read_directory_iterator_set_error(it, impl.name, _get_platform_error())
return
}
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
}
defer if it.err.err != nil {
win32.FindClose(it.impl.find_handle)
}
return dfi[:], nil
err: Error
it.impl.path, err = _cleanpath_from_buf(wpath, file_allocator())
if err != nil {
read_directory_iterator_set_error(it, impl.name, err)
}
return
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it.f == nil {
return
}
file_info_delete(it.impl.prev_fi, file_allocator())
delete(it.impl.path, file_allocator())
win32.FindClose(it.impl.find_handle)
}

View File

@@ -1,30 +1,37 @@
package os
#+private
package os2
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) {
_lookup_env_alloc :: proc(key: string, allocator: runtime.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)
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
return "", true
}
b := make([]u16, n+1, temp_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 {
if n == 0 {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
return "", false
}
value, _ = win32.utf16_to_utf8(b[:n], allocator)
value = win32_utf16_to_utf8(string16(b[:n]), allocator) or_else ""
found = true
return
}
@@ -33,7 +40,7 @@ lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value:
// 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) {
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
key_buf: [513]u16
wkey := win32.utf8_to_wstring(key_buf[:], key)
if wkey == nil {
@@ -57,78 +64,28 @@ lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error)
return value, nil
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
// 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)
_set_env :: proc(key, value: string) -> Error {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator) or_return
v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) {
return get_last_error()
return _get_platform_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
_unset_env :: proc(key: string) -> bool {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, 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)
_clear_env :: proc() {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs, _ := environ(temp_allocator)
for env in envs {
for j in 1..<len(env) {
if env[j] == '=' {
@@ -138,3 +95,48 @@ clear_env :: proc() {
}
}
}
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
envs := win32.GetEnvironmentStringsW()
if envs == nil {
return
}
defer win32.FreeEnvironmentStringsW(envs)
n := 0
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
n += 1
from = i + 1
}
}
r := make([dynamic]string, 0, n, allocator) or_return
defer if err != nil {
for e in r {
delete(e, allocator)
}
delete(r)
}
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
w := ([^]u16)(p)[from:i]
s := win32_utf16_to_utf8(w, allocator) or_return
append(&r, s)
from = i + 1
}
}
environ = r[:]
return
}

View File

@@ -1,13 +1,12 @@
package os
package os2
import "base:intrinsics"
import "base:runtime"
import "core:io"
import "base:runtime"
Platform_Error :: _Platform_Error
#assert(size_of(Platform_Error) <= 4)
#assert(intrinsics.type_has_nil(Platform_Error))
/*
General errors that are common within this package which cannot
be categorized by `io.Error` nor `runtime.Allocator_Error`.
*/
General_Error :: enum u32 {
None,
@@ -22,39 +21,43 @@ General_Error :: enum u32 {
Invalid_Dir,
Invalid_Path,
Invalid_Callback,
Invalid_Command,
Pattern_Has_Separator,
Pattern_Syntax_Error, // Indicates an error in `glob` or `match` pattern.
File_Is_Pipe,
Not_Dir,
// Environment variable not found.
No_HOME_Variable,
Env_Var_Not_Found,
}
// A platform specific error
Platform_Error :: _Platform_Error
Errno :: Error // alias for legacy use
/*
`Error` is a union of different classes of errors that could be returned from procedures in this package.
*/
Error :: union #shared_nil {
General_Error,
io.Error,
runtime.Allocator_Error,
Platform_Error,
}
#assert(size_of(Error) == 8)
#assert(size_of(Error) == size_of(u64))
ERROR_NONE :: Error{}
ERROR_EOF :: io.Error.EOF
// Attempts to convert an `Error` into a platform specific error as an integer. `ok` is false if not possible
@(require_results)
is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {}
return i32(v), i32(v) != 0
}
// Attempts to return the error `ferr` as a string without any allocation
@(require_results)
error_string :: proc "contextless" (ferr: Error) -> string {
error_string :: proc(ferr: Error) -> string {
if ferr == nil {
return ""
}
@@ -62,18 +65,19 @@ error_string :: proc "contextless" (ferr: Error) -> string {
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 .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 .Invalid_Command: return "invalid command"
case .Pattern_Has_Separator: return "pattern has separator"
case .Pattern_Syntax_Error: return "glob pattern syntax error"
case .No_HOME_Variable: return "no $HOME variable"
case .Env_Var_Not_Found: return "environment variable not found"
}
case io.Error:
switch e {
@@ -106,210 +110,35 @@ error_string :: proc "contextless" (ferr: Error) -> string {
case .Mode_Not_Implemented: return "allocator mode not implemented"
}
case Platform_Error:
return _error_string(e)
return _error_string(i32(e))
}
return "unknown error"
}
print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
/*
`print_error` is a utility procedure which will print an error `ferr` to a specified file `f`.
*/
print_error :: proc(f: ^File, ferr: Error, msg: string) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
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]
buf := make([]u8, length, temp_allocator)
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)
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)
// Attempts to convert an `Error` `ferr` into an `io.Error`
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None

View File

@@ -0,0 +1,114 @@
package os
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

@@ -0,0 +1,140 @@
package os
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
}
}
}
}

318
core/os/old/errors.odin Normal file
View File

@@ -0,0 +1,318 @@
package os
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
}

33
core/os/old/stat.odin Normal file
View File

@@ -0,0 +1,33 @@
package os
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

@@ -0,0 +1,303 @@
package os
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,144 +0,0 @@
#+private
package os2
import "base:runtime"
import "core:time"
import win32 "core:sys/windows"
@(private="file")
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
// Ignore "." and ".."
if d.cFileName[0] == '.' && d.cFileName[1] == 0 {
return
}
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, Permissions_Read_Write_All) or_else 0)
defer win32.CloseHandle(handle)
fi.fullpath = path
fi.name = basename(path)
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0)
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))
if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) {
#assert(size_of(fi.inode) == size_of(file_id_info.FileId))
#assert(size_of(fi.inode) == 16)
runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16)
}
return
}
Read_Directory_Iterator_Impl :: struct {
find_data: win32.WIN32_FIND_DATAW,
find_handle: win32.HANDLE,
path: string,
prev_fi: File_Info,
no_more_files: bool,
}
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
for !it.impl.no_more_files {
err: Error
file_info_delete(it.impl.prev_fi, file_allocator())
it.impl.prev_fi = {}
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
if err != nil {
read_directory_iterator_set_error(it, it.impl.path, err)
return
}
if fi.name != "" {
it.impl.prev_fi = fi
ok = true
index = it.index
it.index += 1
}
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
e := _get_platform_error()
if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) {
read_directory_iterator_set_error(it, it.impl.path, e)
}
it.impl.no_more_files = true
}
if ok {
return
}
}
return
}
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
it.impl.no_more_files = false
if f == nil || f.impl == nil {
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
it.f = f
impl := (^File_Impl)(f.impl)
// NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx.
if it.impl.find_handle != nil {
win32.FindClose(it.impl.find_handle)
}
if it.impl.path != "" {
delete(it.impl.path, file_allocator())
}
if !is_directory(impl.name) {
read_directory_iterator_set_error(it, impl.name, .Invalid_Dir)
return
}
wpath := string16(impl.wname)
temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath_search := make([]u16, len(wpath)+3, temp_allocator)
copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*'
wpath_search[len(wpath)+2] = 0
it.impl.find_handle = win32.FindFirstFileW(cstring16(raw_data(wpath_search)), &it.impl.find_data)
if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
read_directory_iterator_set_error(it, impl.name, _get_platform_error())
return
}
defer if it.err.err != nil {
win32.FindClose(it.impl.find_handle)
}
err: Error
it.impl.path, err = _cleanpath_from_buf(wpath, file_allocator())
if err != nil {
read_directory_iterator_set_error(it, impl.name, err)
}
return
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it.f == nil {
return
}
file_info_delete(it.impl.prev_fi, file_allocator())
delete(it.impl.path, file_allocator())
win32.FindClose(it.impl.find_handle)
}

View File

@@ -1,142 +0,0 @@
#+private
package os2
import win32 "core:sys/windows"
import "base:runtime"
_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" {
return
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
return "", true
}
b := make([]u16, n+1, temp_allocator)
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 {
err := win32.GetLastError()
if err == win32.ERROR_ENVVAR_NOT_FOUND {
return "", false
}
return "", false
}
value = win32_utf16_to_utf8(string16(b[:n]), allocator) or_else ""
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_buf :: 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_buf}
_set_env :: proc(key, value: string) -> Error {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator) or_return
v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) {
return _get_platform_error()
}
return nil
}
_unset_env :: proc(key: string) -> bool {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, nil))
}
_clear_env :: proc() {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs, _ := environ(temp_allocator)
for env in envs {
for j in 1..<len(env) {
if env[j] == '=' {
unset_env(env[0:j])
break
}
}
}
}
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
envs := win32.GetEnvironmentStringsW()
if envs == nil {
return
}
defer win32.FreeEnvironmentStringsW(envs)
n := 0
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
n += 1
from = i + 1
}
}
r := make([dynamic]string, 0, n, allocator) or_return
defer if err != nil {
for e in r {
delete(e, allocator)
}
delete(r)
}
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
w := ([^]u16)(p)[from:i]
s := win32_utf16_to_utf8(w, allocator) or_return
append(&r, s)
from = i + 1
}
}
environ = r[:]
return
}

View File

@@ -1,147 +0,0 @@
package os2
import "core:io"
import "base:runtime"
/*
General errors that are common within this package which cannot
be categorized by `io.Error` nor `runtime.Allocator_Error`.
*/
General_Error :: enum u32 {
None,
Exist,
Not_Exist,
Timeout,
Broken_Pipe,
Invalid_File,
Invalid_Dir,
Invalid_Path,
Invalid_Callback,
Invalid_Command,
Pattern_Has_Separator,
Pattern_Syntax_Error, // Indicates an error in `glob` or `match` pattern.
No_HOME_Variable,
Env_Var_Not_Found,
}
// A platform specific error
Platform_Error :: _Platform_Error
/*
`Error` is a union of different classes of errors that could be returned from procedures in this package.
*/
Error :: union #shared_nil {
General_Error,
io.Error,
runtime.Allocator_Error,
Platform_Error,
}
#assert(size_of(Error) == size_of(u64))
ERROR_NONE :: Error{}
// Attempts to convert an `Error` into a platform specific error as an integer. `ok` is false if not possible
@(require_results)
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {}
return i32(v), i32(v) != 0
}
// Attempts to return the error `ferr` as a string without any allocation
@(require_results)
error_string :: proc(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 .Invalid_Command: return "invalid command"
case .Pattern_Has_Separator: return "pattern has separator"
case .Pattern_Syntax_Error: return "glob pattern syntax error"
case .No_HOME_Variable: return "no $HOME variable"
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(i32(e))
}
return "unknown error"
}
/*
`print_error` is a utility procedure which will print an error `ferr` to a specified file `f`.
*/
print_error :: proc(f: ^File, ferr: Error, msg: string) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr)
// msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1
buf := make([]u8, length, temp_allocator)
copy(buf, msg)
buf[len(msg)] = ':'
buf[len(msg) + 1] = ' '
copy(buf[len(msg) + 2:], err_str)
buf[length - 1] = '\n'
write(f, buf)
}
// Attempts to convert an `Error` `ferr` into an `io.Error`
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None
}
return ferr.(io.Error) or_else .Unknown
}

View File

@@ -1,117 +0,0 @@
package os2
import "base:runtime"
import "core:strings"
import "core:time"
Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
/*
`File_Info` describes a file and is returned from `stat`, `fstat`, and `lstat`.
*/
File_Info :: struct {
fullpath: string, // fullpath of the file
name: string, // base name of the file
inode: u128, // might be zero if cannot be determined
size: i64 `fmt:"M"`, // length in bytes for regular files; system-dependent for other file types
mode: Permissions, // file permission flags
type: File_Type,
creation_time: time.Time,
modification_time: time.Time,
access_time: time.Time,
}
@(require_results)
file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
cloned = fi
cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return
_, cloned.name = split_path(cloned.fullpath)
return
}
file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
#reverse for info in infos {
file_info_delete(info, allocator)
}
delete(infos, allocator)
}
file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
delete(fi.fullpath, allocator)
}
@(require_results)
fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
if f == nil {
return {}, nil
} else if f.stream.procedure != nil {
fi: File_Info
data := ([^]byte)(&fi)[:size_of(fi)]
_, err := f.stream.procedure(f, .Fstat, data, 0, nil, allocator)
return fi, err
}
return {}, .Invalid_Callback
}
/*
`stat` returns a `File_Info` describing the named file from the file system.
The resulting `File_Info` must be deleted with `file_info_delete`.
*/
@(require_results)
stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _stat(name, allocator)
}
lstat :: stat_do_not_follow_links
/*
Returns a `File_Info` describing the named file from the file system.
If the file is a symbolic link, the `File_Info` returns describes the symbolic link,
rather than following the link.
The resulting `File_Info` must be deleted with `file_info_delete`.
*/
@(require_results)
stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return _lstat(name, allocator)
}
/*
Returns true if two `File_Info`s are equivalent.
*/
@(require_results)
same_file :: proc(fi1, fi2: File_Info) -> bool {
return _same_file(fi1, fi2)
}
last_write_time :: modification_time
last_write_time_by_name :: modification_time_by_path
/*
Returns the modification time of the file `f`.
The resolution of the timestamp is system-dependent.
*/
@(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := fstat(f, temp_allocator)
return fi.modification_time, err
}
/*
Returns the modification time of the named file `path`.
The resolution of the timestamp is system-dependent.
*/
@(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator)
return fi.modification_time, err
}
is_reserved_name :: proc(path: string) -> bool {
return _is_reserved_name(path)
}

View File

@@ -1,393 +0,0 @@
#+private
package os2
import "base:runtime"
import "core:time"
import "core:strings"
import win32 "core:sys/windows"
_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
if f == nil || (^File_Impl)(f.impl).fd == nil {
return
}
path := _cleanpath_from_handle(f, allocator) or_return
h := _handle(f)
switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR:
fi = File_Info {
fullpath = path,
name = basename(path),
type = file_type(h),
}
return
}
return _file_info_from_get_file_information_by_handle(path, h, allocator)
}
_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator)
}
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) {
return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator)
}
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) {
name := name
if name == "" {
name = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := win32_utf8_to_utf16(name, temp_allocator) or_return
n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil)
if n == 0 {
return "", _get_platform_error()
}
buf := make([]u16, n+1, temp_allocator)
n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil)
if n == 0 {
return "", _get_platform_error()
}
return win32_utf16_to_utf8(buf[:n], allocator)
}
internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
if len(name) == 0 {
return {}, .Not_Exist
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wname := _fix_long_path(name, temp_allocator) or_return
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
fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return
if fi.type == .Undetermined {
fi.type = _file_type_from_create_file(wname, create_file_attributes)
}
return
}
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_platform_error()
return
}
win32.FindClose(sh)
fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return
if fi.type == .Undetermined {
fi.type = _file_type_from_create_file(wname, create_file_attributes)
}
return
}
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
if h == win32.INVALID_HANDLE_VALUE {
e = _get_platform_error()
return
}
defer win32.CloseHandle(h)
return _file_info_from_get_file_information_by_handle(name, h, allocator)
}
_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 {
if buf[0] == '\\' &&
buf[1] == '\\' &&
buf[2] == '?' &&
buf[3] == '\\' {
buf = buf[4:]
}
}
return buf
}
_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
if f == nil {
return "", nil
}
h := _handle(f)
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
if n == 0 {
return "", _get_platform_error()
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0)
return _cleanpath_from_buf(string16(buf[:n]), allocator)
}
_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
if f == nil {
return nil, nil
}
h := _handle(f)
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
if n == 0 {
return nil, _get_platform_error()
}
temp_allocator := TEMP_ALLOCATOR_GUARD({})
buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil
}
_cleanpath_from_buf :: proc(buf: string16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
buf := transmute([]u16)buf
buf = _cleanpath_strip_prefix(buf)
return win32_utf16_to_utf8(buf, allocator)
}
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
}
file_type :: proc(h: win32.HANDLE) -> File_Type {
switch win32.GetFileType(h) {
case win32.FILE_TYPE_PIPE: return .Named_Pipe
case win32.FILE_TYPE_CHAR: return .Character_Device
case win32.FILE_TYPE_DISK: return .Regular
}
return .Undetermined
}
_file_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type {
h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil)
if h == win32.INVALID_HANDLE_VALUE {
return .Undetermined
}
defer win32.CloseHandle(h)
return file_type(h)
}
_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: Permissions) {
if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 {
mode += Permissions_Write_All
} else {
mode += Permissions_Read_Write_All
}
is_sym := false
if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
is_sym = false
} else {
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
}
if is_sym {
type = .Symlink
} else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
type = .Directory
mode += Permissions_Execute_All
} else if h != nil {
type = file_type(h)
}
return
}
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
win := u64(t._nsec / 100) + 116444736000000000
return win32.LARGE_INTEGER(win)
}
filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
return {_nsec=(i64(ft) - 116444736000000000) * 100}
}
filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
}
filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
}
_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) {
d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(h, &d) {
return {}, _get_platform_error()
}
ti: win32.FILE_ATTRIBUTE_TAG_INFO
if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
err := _get_platform_error()
if perr, ok := is_platform_error(err); ok && perr != i32(win32.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.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
fi.type = type
fi.mode |= mode
fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = filetime_as_time(d.ftLastAccessTime)
return fi, nil
}
reserved_names := [?]string{
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
_is_reserved_name :: proc(path: string) -> bool {
if len(path) == 0 {
return false
}
for reserved in reserved_names {
if strings.equal_fold(path, reserved) {
return true
}
}
return false
}
_volume_name_len :: proc(path: string) -> (length: int) {
if len(path) < 2 {
return 0
}
if path[1] == ':' {
switch path[0] {
case 'a'..='z', 'A'..='Z':
return 2
}
}
/*
See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
Further allowed paths can be of the form of:
- \\server\share or \\server\share\more\path
- \\?\C:\...
- \\.\PhysicalDriveX
*/
// Any remaining kind of path has to start with two slashes.
if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
return 0
}
// Device path. The volume name is the whole string
if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
return len(path)
}
// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
prefix := 2
// File namespace.
if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
if _is_path_separator(path[4]) {
// `\\?\\` UNC path in file namespace
prefix = 5
}
if len(path) >= 6 && path[5] == ':' {
switch path[4] {
case 'a'..='z', 'A'..='Z':
return 6
case:
return 0
}
}
}
// UNC path, minimum version of the volume is `\\h\s` for host, share.
// Can also contain an IP address in the host position.
slash_count := 0
for i in prefix..<len(path) {
// Host needs to be at least 1 character
if _is_path_separator(path[i]) && i > 0 {
slash_count += 1
if slash_count == 2 {
return i
}
}
}
return len(path)
}

Some files were not shown because too many files have changed in this diff Show More