diff --git a/core/compress/gzip/doc.odin b/core/compress/gzip/doc.odin
index c20ebc33a..e4b1929dd 100644
--- a/core/compress/gzip/doc.odin
+++ b/core/compress/gzip/doc.odin
@@ -5,6 +5,7 @@ Example:
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.
@@ -22,7 +23,8 @@ Example:
main :: proc() {
// Set up output buffer.
- buf := bytes.Buffer{}
+ buf: bytes.Buffer
+ defer bytes.buffer_destroy(&buf)
stdout :: proc(s: string) {
os.write_string(os.stdout, s)
@@ -31,15 +33,13 @@ Example:
os.write_string(os.stderr, s)
}
- args := os.args
-
- if len(args) < 2 {
+ if len(os.args) < 2 {
stderr("No input file specified.\n")
- err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
+ err := gzip.load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
if err == nil {
- stdout("Displaying test vector: ")
+ stdout("Displaying test vector: \"")
stdout(bytes.buffer_to_string(&buf))
- stdout("\n")
+ stdout("\"\n")
} else {
fmt.printf("gzip.load returned %v\n", err)
}
@@ -47,35 +47,31 @@ Example:
os.exit(0)
}
- // The rest are all files.
- args = args[1:]
- err: Error
+ for file in os.args[1:] {
+ err: gzip.Error
- for file in args {
if file == "-" {
// Read from stdin
- s := os.stream_from_handle(os.stdin)
ctx := &compress.Context_Stream_Input{
- input = s,
+ input = os.stdin.stream,
}
- err = load(ctx, &buf)
+ err = gzip.load(ctx, &buf)
} else {
- err = load(file, &buf)
+ err = gzip.load(file, &buf)
}
- if err != nil {
- if err != E_General.File_Not_Found {
- stderr("File not found: ")
- stderr(file)
- stderr("\n")
- os.exit(1)
- }
+ switch err {
+ case nil:
+ stdout(bytes.buffer_to_string(&buf))
+ case gzip.E_General.File_Not_Found:
+ stderr("File not found: ")
+ stderr(file)
+ stderr("\n")
+ os.exit(1)
+ case:
stderr("GZIP returned an error.\n")
- bytes.buffer_destroy(&buf)
os.exit(2)
}
- stdout(bytes.buffer_to_string(&buf))
}
- bytes.buffer_destroy(&buf)
}
*/
package compress_gzip
diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin
index 7dc8120e4..aedbe3a83 100644
--- a/core/compress/gzip/gzip.odin
+++ b/core/compress/gzip/gzip.odin
@@ -107,14 +107,10 @@ load :: proc{load_from_bytes, load_from_file, load_from_context}
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
- data, ok := os.read_entire_file(filename)
- defer delete(data)
+ file_data, file_err := os.read_entire_file(filename, allocator)
+ defer delete(file_data)
- err = E_General.File_Not_Found
- if ok {
- err = load_from_bytes(data, buf, len(data), expected_output_size)
- }
- return
+ return load_from_bytes(file_data, buf, len(file_data), expected_output_size) if file_err == nil else E_General.File_Not_Found
}
load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
diff --git a/core/crypto/hash/hash_js.odin b/core/crypto/hash/hash_js.odin
new file mode 100644
index 000000000..99309b944
--- /dev/null
+++ b/core/crypto/hash/hash_js.odin
@@ -0,0 +1,10 @@
+#+build js
+package crypto_hash
+
+hash :: proc {
+ hash_stream,
+ hash_bytes,
+ hash_string,
+ hash_bytes_to_buffer,
+ hash_string_to_buffer,
+}
diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin
index d54e657ad..49c1a0ff8 100644
--- a/core/crypto/hash/hash_os.odin
+++ b/core/crypto/hash/hash_os.odin
@@ -1,26 +1,27 @@
#+build !freestanding
+#+build !js
package crypto_hash
import "core:io"
import "core:os"
-// hash_file will read the file provided by the given handle and return the
+// `hash_file` will read the file provided by the given handle and return the
// computed digest in a newly allocated slice.
-hash_file :: proc(
- algorithm: Algorithm,
- hd: os.Handle,
+hash_file_by_handle :: proc(
+ algorithm: Algorithm,
+ handle: ^os.File,
load_at_once := false,
- allocator := context.allocator,
+ allocator := context.allocator,
) -> (
[]byte,
io.Error,
) {
if !load_at_once {
- return hash_stream(algorithm, os.stream_from_handle(hd), allocator)
+ return hash_stream(algorithm, os.to_stream(handle), allocator)
}
- buf, ok := os.read_entire_file(hd, allocator)
- if !ok {
+ buf, err := os.read_entire_file(handle, allocator)
+ if err != nil {
return nil, io.Error.Unknown
}
defer delete(buf, allocator)
@@ -28,11 +29,30 @@ hash_file :: proc(
return hash_bytes(algorithm, buf, allocator), io.Error.None
}
+hash_file_by_name :: proc(
+ algorithm: Algorithm,
+ filename: string,
+ load_at_once := false,
+ allocator := context.allocator,
+) -> (
+ []byte,
+ io.Error,
+) {
+ handle, err := os.open(filename)
+ defer os.close(handle)
+
+ if err != nil {
+ return {}, io.Error.Unknown
+ }
+ return hash_file_by_handle(algorithm, handle, load_at_once, allocator)
+}
+
+
hash :: proc {
hash_stream,
- hash_file,
+ hash_file_by_handle,
hash_bytes,
hash_string,
hash_bytes_to_buffer,
hash_string_to_buffer,
-}
+}
\ No newline at end of file
diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin
index 50b8e3d1a..1fb685602 100644
--- a/core/encoding/csv/doc.odin
+++ b/core/encoding/csv/doc.odin
@@ -16,14 +16,15 @@ Example:
r.reuse_record_buffer = true // Without it you have to each of the fields within it
defer csv.reader_destroy(&r)
- csv_data, ok := os.read_entire_file(filename)
- if ok {
+ csv_data, csv_err := os.read_entire_file(filename, context.allocator)
+ defer delete(csv_data)
+
+ if csv_err == nil {
csv.reader_init_with_string(&r, string(csv_data))
} else {
- fmt.printfln("Unable to open file: %v", filename)
+ fmt.eprintfln("Unable to open file: %v. Error: %v", filename, csv_err)
return
}
- defer delete(csv_data)
for r, i, err in csv.iterator_next(&r) {
if err != nil { /* Do something with error */ }
@@ -39,16 +40,16 @@ Example:
r: csv.Reader
r.trim_leading_space = true
r.reuse_record = true // Without it you have to delete(record)
- r.reuse_record_buffer = true // Without it you have to each of the fields within it
+ r.reuse_record_buffer = true // Without it you have to delete each of the fields within it
defer csv.reader_destroy(&r)
handle, err := os.open(filename)
+ defer os.close(handle)
if err != nil {
- fmt.eprintfln("Error opening file: %v", filename)
+ fmt.eprintfln("Unable to open file: %v. Error: %v", filename, err)
return
}
- defer os.close(handle)
- csv.reader_init(&r, os.stream_from_handle(handle))
+ csv.reader_init(&r, handle.stream)
for r, i in csv.iterator_next(&r) {
for f, j in r {
@@ -64,21 +65,23 @@ Example:
r.trim_leading_space = true
defer csv.reader_destroy(&r)
- csv_data, ok := os.read_entire_file(filename)
- if ok {
- csv.reader_init_with_string(&r, string(csv_data))
- } else {
- fmt.printfln("Unable to open file: %v", filename)
+ csv_data, csv_err := os.read_entire_file(filename, context.allocator)
+ defer delete(csv_data, context.allocator)
+ if err != nil {
+ fmt.eprintfln("Unable to open file: %v. Error: %v", filename, csv_err)
return
}
- defer delete(csv_data)
+ csv.reader_init_with_string(&r, string(csv_data))
records, err := csv.read_all(&r)
if err != nil { /* Do something with CSV parse error */ }
defer {
- for rec in records {
- delete(rec)
+ for record in records {
+ for field in record {
+ delete(field)
+ }
+ delete(record)
}
delete(records)
}
diff --git a/core/encoding/hxa/hxa_os.odin b/core/encoding/hxa/hxa_os.odin
new file mode 100644
index 000000000..17ad94819
--- /dev/null
+++ b/core/encoding/hxa/hxa_os.odin
@@ -0,0 +1,34 @@
+#+build !freestanding
+#+build !js
+package encoding_hxa
+
+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
+
+ data, data_err := os.read_entire_file(filename, allocator, loc)
+ if data_err != nil {
+ err = .Unable_To_Read_File
+ delete(data, allocator)
+ return
+ }
+ file, err = read(data, filename, print_error, allocator)
+ file.backing = data
+ return
+}
+
+write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) {
+ required := required_write_size(file)
+ buf, alloc_err := make([]byte, required)
+ if alloc_err == .Out_Of_Memory {
+ return .Failed_File_Write
+ }
+ defer delete(buf)
+
+ write_internal(&Writer{data = buf}, file)
+ if os.write_entire_file(filepath, buf) != nil {
+ err =.Failed_File_Write
+ }
+ return
+}
diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin
index 6dde16848..1721bf7fc 100644
--- a/core/encoding/hxa/read.odin
+++ b/core/encoding/hxa/read.odin
@@ -1,7 +1,6 @@
package encoding_hxa
import "core:fmt"
-import "core:os"
import "core:mem"
Read_Error :: enum {
@@ -11,20 +10,6 @@ Read_Error :: enum {
Unable_To_Read_File,
}
-read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
- context.allocator = allocator
-
- data, ok := os.read_entire_file(filename, allocator, loc)
- if !ok {
- err = .Unable_To_Read_File
- delete(data, allocator, loc)
- return
- }
- file, err = read(data, filename, print_error, allocator, loc)
- file.backing = data
- return
-}
-
read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
Reader :: struct {
filename: string,
diff --git a/core/encoding/hxa/write.odin b/core/encoding/hxa/write.odin
index 5bb950e81..cbf9c7cb6 100644
--- a/core/encoding/hxa/write.odin
+++ b/core/encoding/hxa/write.odin
@@ -1,7 +1,6 @@
package encoding_hxa
-import "core:os"
-import "core:mem"
+import "core:mem"
Write_Error :: enum {
None,
@@ -9,21 +8,6 @@ Write_Error :: enum {
Failed_File_Write,
}
-write_to_file :: proc(filepath: string, file: File) -> (err: Write_Error) {
- required := required_write_size(file)
- buf, alloc_err := make([]byte, required)
- if alloc_err == .Out_Of_Memory {
- return .Failed_File_Write
- }
- defer delete(buf)
-
- write_internal(&Writer{data = buf}, file)
- if !os.write_entire_file(filepath, buf) {
- err =.Failed_File_Write
- }
- return
-}
-
write :: proc(buf: []byte, file: File) -> (n: int, err: Write_Error) {
required := required_write_size(file)
if len(buf) < required {
diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin
index a119b0f2e..8bf6c6c9a 100644
--- a/core/encoding/ini/ini.odin
+++ b/core/encoding/ini/ini.odin
@@ -1,13 +1,12 @@
// Reader and writer for a variant of the `.ini` file format with `key = value` entries in `[sections]`.
package encoding_ini
-import "base:runtime"
-import "base:intrinsics"
-import "core:strings"
-import "core:strconv"
-import "core:io"
-import "core:os"
-import "core:fmt"
+import "base:runtime"
+import "base:intrinsics"
+import "core:strings"
+import "core:strconv"
+import "core:io"
+import "core:fmt"
_ :: fmt
Options :: struct {
@@ -120,17 +119,6 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options
return
}
-load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) {
- data := os.read_entire_file(path, allocator) or_return
- defer delete(data, allocator)
- m, err = load_map_from_string(string(data), allocator, options)
- ok = err == nil
- defer if !ok {
- delete_map(m)
- }
- return
-}
-
save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) {
b := strings.builder_make(allocator)
_, _ = write_map(strings.to_writer(&b), m)
@@ -191,4 +179,4 @@ write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) {
section_index += 1
}
return
-}
+}
\ No newline at end of file
diff --git a/core/encoding/ini/ini_os.odin b/core/encoding/ini/ini_os.odin
new file mode 100644
index 000000000..22c6bf7b3
--- /dev/null
+++ b/core/encoding/ini/ini_os.odin
@@ -0,0 +1,20 @@
+#+build !freestanding
+#+build !js
+package encoding_ini
+
+import "base:runtime"
+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)
+ defer delete(data, allocator)
+ if data_err != nil {
+ return
+ }
+ m, err = load_map_from_string(string(data), allocator, options)
+ ok = err == nil
+ defer if !ok {
+ delete_map(m)
+ }
+ return
+}
diff --git a/core/encoding/xml/xml_os.odin b/core/encoding/xml/xml_os.odin
new file mode 100644
index 000000000..1e94572c6
--- /dev/null
+++ b/core/encoding/xml/xml_os.odin
@@ -0,0 +1,18 @@
+#+build !freestanding
+#+build !js
+package encoding_xml
+
+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) {
+ context.allocator = allocator
+ options := options
+
+ data, data_err := os.read_entire_file(filename, allocator)
+ if data_err != nil { return {}, .File_Error }
+
+ options.flags += { .Input_May_Be_Modified }
+
+ return parse_bytes(data, options, filename, error_handler, allocator)
+}
diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin
index 8f8fffe14..6d068466b 100644
--- a/core/encoding/xml/xml_reader.odin
+++ b/core/encoding/xml/xml_reader.odin
@@ -9,13 +9,12 @@ package encoding_xml
- Jeroen van Rijn: Initial implementation.
*/
-import "core:bytes"
-import "core:encoding/entity"
-import "base:intrinsics"
-import "core:mem"
-import "core:os"
-import "core:strings"
-import "base:runtime"
+import "base:runtime"
+import "core:bytes"
+import "core:encoding/entity"
+import "base:intrinsics"
+import "core:mem"
+import "core:strings"
likely :: intrinsics.expect
@@ -373,19 +372,6 @@ parse_string :: proc(data: string, options := DEFAULT_OPTIONS, path := "", error
parse :: proc { parse_string, parse_bytes }
-// 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) {
- context.allocator = allocator
- options := options
-
- data, data_ok := os.read_entire_file(filename)
- if !data_ok { return {}, .File_Error }
-
- options.flags += { .Input_May_Be_Modified }
-
- return parse_bytes(data, options, filename, error_handler, allocator)
-}
-
destroy :: proc(doc: ^Document, allocator := context.allocator) {
context.allocator = allocator
if doc == nil { return }
diff --git a/core/flags/errors.odin b/core/flags/errors.odin
index e9b2e18c8..efe4cb6c4 100644
--- a/core/flags/errors.odin
+++ b/core/flags/errors.odin
@@ -37,8 +37,8 @@ Unified_Parse_Error_Reason :: union #shared_nil {
Open_File_Error :: struct {
filename: string,
errno: os.Error,
- mode: int,
- perms: int,
+ flags: os.File_Flags,
+ perms: os.Permissions,
}
// Raised during parsing.
diff --git a/core/flags/example/example.odin b/core/flags/example/example.odin
index a3af44790..6ace3d852 100644
--- a/core/flags/example/example.odin
+++ b/core/flags/example/example.odin
@@ -76,8 +76,8 @@ Distinct_Int :: distinct int
main :: proc() {
Options :: struct {
- file: os.Handle `args:"pos=0,required,file=r" usage:"Input file."`,
- output: os.Handle `args:"pos=1,file=cw" usage:"Output file."`,
+ file: ^os.File `args:"pos=0,required,file=r" usage:"Input file."`,
+ output: ^os.File `args:"pos=1,file=cw" usage:"Output file."`,
hub: net.Host_Or_Endpoint `usage:"Internet address to contact for updates."`,
schedule: datetime.DateTime `usage:"Launch tasks at this time."`,
@@ -126,7 +126,7 @@ main :: proc() {
fmt.printfln("%#v", opt)
- if opt.output != 0 {
+ if opt.output != nil {
os.write_string(opt.output, "Hellope!\n")
}
}
diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin
index b3880afa0..1d86f4bce 100644
--- a/core/flags/internal_rtti.odin
+++ b/core/flags/internal_rtti.odin
@@ -1,18 +1,18 @@
#+private
package flags
-import "base:intrinsics"
-import "base:runtime"
-import "core:fmt"
-import "core:mem"
-import "core:net"
-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"
+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 {
@@ -209,7 +209,7 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info:
parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type: typeid, arg_tag: string, out_error: ^Error) {
// Core types currently supported:
//
- // - os.Handle
+ // - ^os.File
// - time.Time
// - datetime.DateTime
// - net.Host_Or_Endpoint
@@ -217,64 +217,61 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type:
GENERIC_RFC_3339_ERROR :: "Invalid RFC 3339 string. Try this format: `yyyy-mm-ddThh:mm:ssZ`, for example `2024-02-29T16:30:00Z`."
out_error^ = nil
-
- if data_type == os.Handle {
+ if data_type == ^os.File {
// NOTE: `os` is hopefully available everywhere, even if it might panic on some calls.
- wants_read := false
- wants_write := false
- mode: int
+ flags: os.File_Flags
if file, ok := get_struct_subtag(arg_tag, SUBTAG_FILE); ok {
for i in 0..= {.Read, .Write} {
+ octal_perms |= 0o200
+ } else if .Write in flags {
+ octal_perms |= 0o200
} else {
- mode |= os.O_RDONLY
+ flags |= {.Read}
}
if permstr, ok := get_struct_subtag(arg_tag, SUBTAG_PERMS); ok {
if value, parse_ok := strconv.parse_u64_of_base(permstr, 8); parse_ok {
- perms = int(value)
+ octal_perms = int(value)
}
}
- handle, errno := os.open(str, mode, perms)
- if errno != nil {
+ perms := os.perm(octal_perms)
+
+ f, error := os.open(str, flags, perms)
+ if error != nil {
// NOTE(Feoramund): os.Error is system-dependent, and there's
// currently no good way to translate them all into strings.
//
- // The upcoming `os2` package will hopefully solve this.
+ // The upcoming `core:os` package will hopefully solve this.
//
// We can at least provide the number for now, so the user can look
// it up.
out_error^ = Open_File_Error {
str,
- errno,
- mode,
+ error,
+ flags,
perms,
}
return
}
- (^os.Handle)(ptr)^ = handle
+ (^^os.File)(ptr)^ = f
return
}
@@ -475,6 +472,11 @@ parse_and_set_pointer_by_type :: proc(ptr: rawptr, str: string, type_info: ^runt
}
case:
+ if type_info.id == ^os.File {
+ parse_and_set_pointer_by_named_type(ptr, str, type_info.id, arg_tag, &error)
+ return
+ }
+
if !parse_and_set_pointer_by_base_type(ptr, str, type_info) {
return Parse_Error {
// The caller will add more details.
diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin
index cd903c3e5..6f9016a21 100644
--- a/core/flags/internal_validation.odin
+++ b/core/flags/internal_validation.odin
@@ -138,20 +138,20 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
allowed_to_define_file_perms: bool = ---
#partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Map:
- allowed_to_define_file_perms = specific_type_info.value.id == os.Handle
+ allowed_to_define_file_perms = specific_type_info.value.id == ^os.File
case runtime.Type_Info_Dynamic_Array:
- allowed_to_define_file_perms = specific_type_info.elem.id == os.Handle
+ allowed_to_define_file_perms = specific_type_info.elem.id == ^os.File
case:
- allowed_to_define_file_perms = field.type.id == os.Handle
+ allowed_to_define_file_perms = field.type.id == ^os.File
}
if _, has_file := get_struct_subtag(args_tag, SUBTAG_FILE); has_file {
- fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
+ fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `^os.File` type.",
model_type, field.name, SUBTAG_FILE, loc = loc)
}
if _, has_perms := get_struct_subtag(args_tag, SUBTAG_PERMS); has_perms {
- fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `os.Handle` type.",
+ fmt.assertf(allowed_to_define_file_perms, "%T.%s has `%s` defined, but it is not nor does it contain an `^os.File` type.",
model_type, field.name, SUBTAG_PERMS, loc = loc)
}
diff --git a/core/flags/util.odin b/core/flags/util.odin
index ce7e2e36c..0d18fa196 100644
--- a/core/flags/util.odin
+++ b/core/flags/util.odin
@@ -1,9 +1,9 @@
package flags
-import "core:fmt"
+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.
@@ -38,7 +38,7 @@ parse_or_exit :: proc(
error := parse(model, args, style, true, true, allocator, loc)
if error != nil {
- stderr := os.stream_from_handle(os.stderr)
+ stderr := os.to_stream(os.stderr)
if len(args) == 0 {
// No arguments entered, and there was an error; show the usage,
@@ -65,18 +65,18 @@ Inputs:
*/
@(optimization_mode="favor_size")
print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) {
- stderr := os.stream_from_handle(os.stderr)
- stdout := os.stream_from_handle(os.stdout)
+ stderr := os.to_stream(os.stderr)
+ stdout := os.to_stream(os.stdout)
switch specific_error in error {
case Parse_Error:
fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message)
case Open_File_Error:
- fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s",
+ fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o and flags %v: %s",
specific_error,
specific_error.errno,
specific_error.perms,
- specific_error.mode,
+ specific_error.flags,
specific_error.filename)
case Validation_Error:
fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message)
diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin
index 4ec6bd9a8..ccab15220 100644
--- a/core/fmt/fmt_js.odin
+++ b/core/fmt/fmt_js.odin
@@ -1,10 +1,6 @@
#+build js
package fmt
-import "core:bufio"
-import "core:io"
-import "core:os"
-
foreign import "odin_env"
@(private="file")
@@ -12,90 +8,77 @@ foreign odin_env {
write :: proc "contextless" (fd: u32, p: []byte) ---
}
-@(private="file")
-write_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
- if mode == .Write {
- fd := u32(uintptr(stream_data))
- write(fd, p)
- return i64(len(p)), nil
- }
- return 0, .Unsupported
-}
+stdout :: u32(1)
+stderr :: u32(2)
@(private="file")
-stdout := io.Writer{
- procedure = write_stream_proc,
- data = rawptr(uintptr(1)),
-}
-@(private="file")
-stderr := io.Writer{
- procedure = write_stream_proc,
- data = rawptr(uintptr(2)),
-}
+BUF_SIZE :: 1024
@(private="file")
-fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer {
- switch fd {
- case 1: return stdout
- case 2: return stderr
- case: panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc)
+// TODO: Find a way to grow this if necessary
+buf: [BUF_SIZE]byte
+
+@(private="file")
+get_fd :: proc(f: any, loc := #caller_location) -> (fd: u32) {
+ if _fd, _ok := f.(u32); _ok {
+ fd = _fd
}
+ if fd != 1 && fd != 2 {
+ panic("`fmt.fprint` variant called with invalid file descriptor for JS, only 1 (stdout) and 2 (stderr) are supported", loc)
+ }
+ return fd
}
// fprint formats using the default print settings and writes to fd
-fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int {
- buf: [1024]byte
- b: bufio.Writer
- defer bufio.writer_flush(&b)
-
- bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
- w := bufio.writer_to_writer(&b)
- return wprint(w, ..args, sep=sep, flush=flush)
+// flush is ignored
+fprint :: proc(f: any, args: ..any, sep := " ", flush := true, loc := #caller_location) -> (n: int) {
+ fd := get_fd(f)
+ s := bprint(buf[:], ..args, sep=sep)
+ n = len(s)
+ write(fd, transmute([]byte)s)
+ return n
}
-// fprintln formats using the default print settings and writes to fd
-fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int {
- buf: [1024]byte
- b: bufio.Writer
- defer bufio.writer_flush(&b)
-
- bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
-
- w := bufio.writer_to_writer(&b)
- return wprintln(w, ..args, sep=sep, flush=flush)
+// fprintln formats using the default print settings and writes to fd, followed by a newline
+// flush is ignored
+fprintln :: proc(f: any, args: ..any, sep := " ", flush := true, loc := #caller_location) -> (n: int) {
+ fd := get_fd(f)
+ s := bprintln(buf[:], ..args, sep=sep)
+ n = len(s)
+ write(fd, transmute([]byte)s)
+ return n
}
// fprintf formats according to the specified format string and writes to fd
-fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int {
- buf: [1024]byte
- b: bufio.Writer
- defer bufio.writer_flush(&b)
-
- bufio.writer_init_with_buf(&b, fd_to_writer(fd, loc), buf[:])
-
- w := bufio.writer_to_writer(&b)
- return wprintf(w, fmt, ..args, flush=flush, newline=newline)
+// flush is ignored
+fprintf :: proc(f: any, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> (n: int) {
+ fd := get_fd(f)
+ s := bprintf(buf[:], fmt, ..args, newline=newline)
+ n = len(s)
+ write(fd, transmute([]byte)s)
+ return n
}
// fprintfln formats according to the specified format string and writes to fd, followed by a newline.
-fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int {
- return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc)
+// flush is ignored
+fprintfln :: proc(f: any, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int {
+ return fprintf(f, fmt, ..args, flush=flush, newline=true, loc=loc)
}
// print formats using the default print settings and writes to stdout
-print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) }
+print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(stdout, ..args, sep=sep, flush=flush) }
// println formats using the default print settings and writes to stdout
-println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) }
+println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(stdout, ..args, sep=sep, flush=flush) }
// printf formats according to the specififed format string and writes to stdout
-printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) }
+printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stdout, fmt, ..args, flush=flush) }
// printfln formats according to the specified format string and writes to stdout, followed by a newline.
-printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
+printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stdout, fmt, ..args, flush=flush, newline=true) }
// eprint formats using the default print settings and writes to stderr
-eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) }
+eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(stderr, ..args, sep=sep, flush=flush) }
// eprintln formats using the default print settings and writes to stderr
-eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) }
+eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(stderr, ..args, sep=sep, flush=flush) }
// eprintf formats according to the specififed format string and writes to stderr
-eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) }
+eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stderr, fmt, ..args, flush=flush) }
// eprintfln formats according to the specified format string and writes to stderr, followed by a newline.
-eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) }
+eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(stderr, fmt, ..args, flush=flush, newline=true) }
\ No newline at end of file
diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin
index a481061f1..0305b5bac 100644
--- a/core/fmt/fmt_os.odin
+++ b/core/fmt/fmt_os.odin
@@ -8,59 +8,61 @@ 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.
+
// fprint formats using the default print settings and writes to fd
-fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int {
+fprint :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
- bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:])
+ bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
return wprint(w, ..args, sep=sep, flush=flush)
}
// fprintln formats using the default print settings and writes to fd
-fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int {
+fprintln :: proc(f: ^os.File, args: ..any, sep := " ", flush := true) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
- bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:])
+ bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
return wprintln(w, ..args, sep=sep, flush=flush)
}
// fprintf formats according to the specified format string and writes to fd
-fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false) -> int {
+fprintf :: proc(f: ^os.File, fmt: string, args: ..any, flush := true, newline := false) -> int {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
- bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:])
+ bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
return wprintf(w, fmt, ..args, flush=flush, newline=newline)
}
// fprintfln formats according to the specified format string and writes to fd, followed by a newline.
-fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true) -> int {
- return fprintf(fd, fmt, ..args, flush=flush, newline=true)
+fprintfln :: proc(f: ^os.File, fmt: string, args: ..any, flush := true) -> int {
+ return fprintf(f, fmt, ..args, flush=flush, newline=true)
}
-fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) {
+fprint_type :: proc(f: ^os.File, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
- bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:])
+ bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
return wprint_type(w, info, flush=flush)
}
-fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: io.Error) {
+fprint_typeid :: proc(f: ^os.File, id: typeid, flush := true) -> (n: int, err: io.Error) {
buf: [1024]byte
b: bufio.Writer
defer bufio.writer_flush(&b)
- bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:])
+ bufio.writer_init_with_buf(&b, os.to_stream(f), buf[:])
w := bufio.writer_to_writer(&b)
return wprint_typeid(w, id, flush=flush)
diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin
index 70a85a784..1aa1d63de 100644
--- a/core/image/bmp/bmp_os.odin
+++ b/core/image/bmp/bmp_os.odin
@@ -9,10 +9,10 @@ load :: proc{load_from_file, load_from_bytes, load_from_context}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
- data, ok := os.read_entire_file(filename)
- defer delete(data)
+ data, data_err := os.read_entire_file(filename, allocator)
+ defer delete(data, allocator)
- if ok {
+ if data_err == nil {
return load_from_bytes(data, options)
} else {
return nil, .Unable_To_Read_File
@@ -28,7 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
defer bytes.buffer_destroy(out)
save_to_buffer(out, img, options) or_return
- write_ok := os.write_entire_file(output, out.buf[:])
+ write_err := os.write_entire_file(output, out.buf[:])
- return nil if write_ok else .Unable_To_Write_File
+ return nil if write_err == nil else .Unable_To_Write_File
}
\ No newline at end of file
diff --git a/core/image/general_os.odin b/core/image/general_os.odin
index 98eb5bdbe..63d7c8d43 100644
--- a/core/image/general_os.odin
+++ b/core/image/general_os.odin
@@ -8,18 +8,16 @@ load :: proc{
load_from_file,
}
-
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
- data, ok := os.read_entire_file(filename, allocator)
+ data, data_err := os.read_entire_file(filename, allocator)
defer delete(data, allocator)
- if ok {
+ if data_err == nil {
return load_from_bytes(data, options, allocator)
} else {
return nil, .Unable_To_Read_File
}
}
-
which :: proc{
which_bytes,
which_file,
diff --git a/core/image/jpeg/jpeg_os.odin b/core/image/jpeg/jpeg_os.odin
index 92c0bb447..6ba301d80 100644
--- a/core/image/jpeg/jpeg_os.odin
+++ b/core/image/jpeg/jpeg_os.odin
@@ -8,10 +8,10 @@ load :: proc{load_from_file, load_from_bytes, load_from_context}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
- data, ok := os.read_entire_file(filename)
- defer delete(data)
+ data, data_err := os.read_entire_file(filename, allocator)
+ defer delete(data, allocator)
- if ok {
+ if data_err == nil {
return load_from_bytes(data, options)
} else {
return nil, .Unable_To_Read_File
diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin
index 2cf2439ac..ae9029b54 100644
--- a/core/image/netpbm/netpbm_os.odin
+++ b/core/image/netpbm/netpbm_os.odin
@@ -8,20 +8,18 @@ load :: proc {
load_from_bytes,
}
-
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
- data, ok := os.read_entire_file(filename); defer delete(data)
- if !ok {
+ data, data_err := os.read_entire_file(filename, allocator); defer delete(data)
+ if data_err == nil {
+ return load_from_bytes(data)
+ } else {
err = .Unable_To_Read_File
return
}
-
- return load_from_bytes(data)
}
-
save :: proc {
save_to_file,
save_to_buffer,
@@ -33,7 +31,7 @@ save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allo
data: []byte; defer delete(data)
data = save_to_buffer(img, custom_info) or_return
- if ok := os.write_entire_file(filename, data); !ok {
+ if save_err := os.write_entire_file(filename, data); save_err != nil {
return .Unable_To_Write_File
}
diff --git a/core/image/png/doc.odin b/core/image/png/doc.odin
deleted file mode 100644
index 034a6775f..000000000
--- a/core/image/png/doc.odin
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
-Reader for `PNG` images.
-
-The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]].
-
-Example:
- package main
-
- import "core:image"
- // import "core:image/png"
- import "core:bytes"
- import "core:fmt"
-
- // For PPM writer
- import "core:mem"
- import "core:os"
-
- main :: proc() {
- track := mem.Tracking_Allocator{}
- mem.tracking_allocator_init(&track, context.allocator)
-
- context.allocator = mem.tracking_allocator(&track)
-
- demo()
-
- if len(track.allocation_map) > 0 {
- fmt.println("Leaks:")
- for _, v in track.allocation_map {
- fmt.printf("\t%v\n\n", v)
- }
- }
- }
-
- demo :: proc() {
- file: string
-
- options := image.Options{.return_metadata}
- err: image.Error
- img: ^image.Image
-
- file = "../../../misc/logo-slim.png"
-
- img, err = load(file, options)
- defer destroy(img)
-
- if err != nil {
- fmt.printf("Trying to read PNG file %v returned %v\n", file, err)
- } else {
- fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth)
-
- if v, ok := img.metadata.(^image.PNG_Info); ok {
- // Handle ancillary chunks as you wish.
- // We provide helper functions for a few types.
- for c in v.chunks {
- #partial switch c.header.type {
- case .tIME:
- if t, t_ok := core_time(c); t_ok {
- fmt.printf("[tIME]: %v\n", t)
- }
- case .gAMA:
- if gama, gama_ok := gamma(c); gama_ok {
- fmt.printf("[gAMA]: %v\n", gama)
- }
- case .pHYs:
- if phys, phys_ok := phys(c); phys_ok {
- if phys.unit == .Meter {
- xm := f32(img.width) / f32(phys.ppu_x)
- ym := f32(img.height) / f32(phys.ppu_y)
- dpi_x, dpi_y := phys_to_dpi(phys)
- fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y)
- fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y)
- fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
- } else {
- fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
- }
- }
- case .iTXt, .zTXt, .tEXt:
- res, ok_text := text(c)
- if ok_text {
- if c.header.type == .iTXt {
- fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text)
- } else {
- fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text)
- }
- }
- defer text_destroy(res)
- case .bKGD:
- fmt.printf("[bKGD] %v\n", img.background)
- case .eXIf:
- if res, ok_exif := exif(c); ok_exif {
- /*
- Other than checking the signature and byte order, we don't handle Exif data.
- If you wish to interpret it, pass it to an Exif parser.
- */
- fmt.printf("[eXIf] %v\n", res)
- }
- case .PLTE:
- if plte, plte_ok := plte(c); plte_ok {
- fmt.printf("[PLTE] %v\n", plte)
- } else {
- fmt.printf("[PLTE] Error\n")
- }
- case .hIST:
- if res, ok_hist := hist(c); ok_hist {
- fmt.printf("[hIST] %v\n", res)
- }
- case .cHRM:
- if res, ok_chrm := chrm(c); ok_chrm {
- fmt.printf("[cHRM] %v\n", res)
- }
- case .sPLT:
- res, ok_splt := splt(c)
- if ok_splt {
- fmt.printf("[sPLT] %v\n", res)
- }
- splt_destroy(res)
- case .sBIT:
- if res, ok_sbit := sbit(c); ok_sbit {
- fmt.printf("[sBIT] %v\n", res)
- }
- case .iCCP:
- res, ok_iccp := iccp(c)
- if ok_iccp {
- fmt.printf("[iCCP] %v\n", res)
- }
- iccp_destroy(res)
- case .sRGB:
- if res, ok_srgb := srgb(c); ok_srgb {
- fmt.printf("[sRGB] Rendering intent: %v\n", res)
- }
- case:
- type := c.header.type
- name := chunk_type_to_name(&type)
- fmt.printf("[%v]: %v\n", name, c.data)
- }
- }
- }
- }
-
- fmt.printf("Done parsing metadata.\n")
-
- if err == nil && .do_not_decompress_image not_in options && .info not_in options {
- if ok := write_image_as_ppm("out.ppm", img); ok {
- fmt.println("Saved decoded image.")
- } else {
- fmt.println("Error saving out.ppm.")
- fmt.println(img)
- }
- }
- }
-
- // Crappy PPM writer used during testing. Don't use in production.
- write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) {
-
- _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) {
- if v, ok := bg.?; ok {
- res = v
- } else {
- if high {
- l := u16(30 * 256 + 30)
-
- if (x & 4 == 0) ~ (y & 4 == 0) {
- res = [3]u16{l, 0, l}
- } else {
- res = [3]u16{l >> 1, 0, l >> 1}
- }
- } else {
- if (x & 4 == 0) ~ (y & 4 == 0) {
- res = [3]u16{30, 30, 30}
- } else {
- res = [3]u16{15, 15, 15}
- }
- }
- }
- return
- }
-
- // profiler.timed_proc();
- using image
- using os
-
- flags: int = O_WRONLY|O_CREATE|O_TRUNC
-
- img := image
-
- // PBM 16-bit images are big endian
- when ODIN_ENDIAN == .Little {
- if img.depth == 16 {
- // The pixel components are in Big Endian. Let's byteswap back.
- input := mem.slice_data_cast([]u16, img.pixels.buf[:])
- output := mem.slice_data_cast([]u16be, img.pixels.buf[:])
- #no_bounds_check for v, i in input {
- output[i] = u16be(v)
- }
- }
- }
-
- pix := bytes.buffer_to_bytes(&img.pixels)
-
- if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) {
- return false
- }
-
- mode: int = 0
- when ODIN_OS == .Linux || ODIN_OS == .Darwin {
- // NOTE(justasd): 644 (owner read, write; group read; others read)
- mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
- }
-
- fd, err := open(filename, flags, mode)
- if err != nil {
- return false
- }
- defer close(fd)
-
- write_string(fd,
- fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)),
- )
-
- if channels == 3 {
- // We don't handle transparency here...
- write_ptr(fd, raw_data(pix), len(pix))
- } else {
- bpp := depth == 16 ? 2 : 1
- bytes_needed := width * height * 3 * bpp
-
- op := bytes.Buffer{}
- bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed)
- defer bytes.buffer_destroy(&op)
-
- if channels == 1 {
- if depth == 16 {
- assert(len(pix) == width * height * 2)
- p16 := mem.slice_data_cast([]u16, pix)
- o16 := mem.slice_data_cast([]u16, op.buf[:])
- #no_bounds_check for len(p16) != 0 {
- r := u16(p16[0])
- o16[0] = r
- o16[1] = r
- o16[2] = r
- p16 = p16[1:]
- o16 = o16[3:]
- }
- } else {
- o := 0
- for i := 0; i < len(pix); i += 1 {
- r := pix[i]
- op.buf[o ] = r
- op.buf[o+1] = r
- op.buf[o+2] = r
- o += 3
- }
- }
- write_ptr(fd, raw_data(op.buf), len(op.buf))
- } else if channels == 2 {
- if depth == 16 {
- p16 := mem.slice_data_cast([]u16, pix)
- o16 := mem.slice_data_cast([]u16, op.buf[:])
-
- bgcol := img.background
-
- #no_bounds_check for len(p16) != 0 {
- r := f64(u16(p16[0]))
- bg: f64
- if bgcol != nil {
- v := bgcol.([3]u16)[0]
- bg = f64(v)
- }
- a := f64(u16(p16[1])) / 65535.0
- l := (a * r) + (1 - a) * bg
-
- o16[0] = u16(l)
- o16[1] = u16(l)
- o16[2] = u16(l)
-
- p16 = p16[2:]
- o16 = o16[3:]
- }
- } else {
- o := 0
- for i := 0; i < len(pix); i += 2 {
- r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0
- c := u8(f32(r) * a1)
- op.buf[o ] = c
- op.buf[o+1] = c
- op.buf[o+2] = c
- o += 3
- }
- }
- write_ptr(fd, raw_data(op.buf), len(op.buf))
- } else if channels == 4 {
- if depth == 16 {
- p16 := mem.slice_data_cast([]u16be, pix)
- o16 := mem.slice_data_cast([]u16be, op.buf[:])
-
- #no_bounds_check for len(p16) != 0 {
-
- bg := _bg(img.background, 0, 0)
- r := f32(p16[0])
- g := f32(p16[1])
- b := f32(p16[2])
- a := f32(p16[3]) / 65535.0
-
- lr := (a * r) + (1 - a) * f32(bg[0])
- lg := (a * g) + (1 - a) * f32(bg[1])
- lb := (a * b) + (1 - a) * f32(bg[2])
-
- o16[0] = u16be(lr)
- o16[1] = u16be(lg)
- o16[2] = u16be(lb)
-
- p16 = p16[4:]
- o16 = o16[3:]
- }
- } else {
- o := 0
-
- for i := 0; i < len(pix); i += 4 {
-
- x := (i / 4) % width
- y := i / width / 4
-
- _b := _bg(img.background, x, y, false)
- bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])}
-
- r := f32(pix[i])
- g := f32(pix[i+1])
- b := f32(pix[i+2])
- a := f32(pix[i+3]) / 255.0
-
- lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0]))
- lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1]))
- lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2]))
- op.buf[o ] = lr
- op.buf[o+1] = lg
- op.buf[o+2] = lb
- o += 3
- }
- }
- write_ptr(fd, raw_data(op.buf), len(op.buf))
- } else {
- return false
- }
- }
- return true
- }
-*/
-package png
diff --git a/core/image/png/example.odin b/core/image/png/example.odin
new file mode 100644
index 000000000..cab6e4de1
--- /dev/null
+++ b/core/image/png/example.odin
@@ -0,0 +1,136 @@
+#+build ignore
+package png_example
+
+import "core:image"
+import "core:image/png"
+import "core:image/tga"
+import "core:fmt"
+import "core:mem"
+
+demo :: proc() {
+ options := image.Options{.return_metadata}
+ err: image.Error
+ img: ^image.Image
+
+ PNG_FILE :: ODIN_ROOT + "misc/logo-slim.png"
+
+ img, err = png.load(PNG_FILE, options)
+ defer png.destroy(img)
+
+ if err != nil {
+ fmt.eprintfln("Trying to read PNG file %v returned %v.", PNG_FILE, err)
+ } else {
+ fmt.printfln("Image: %vx%vx%v, %v-bit.", img.width, img.height, img.channels, img.depth)
+
+ if v, ok := img.metadata.(^image.PNG_Info); ok {
+ // Handle ancillary chunks as you wish.
+ // We provide helper functions for a few types.
+ for c in v.chunks {
+ #partial switch c.header.type {
+ case .tIME:
+ if t, t_ok := png.core_time(c); t_ok {
+ fmt.printfln("[tIME]: %v", t)
+ }
+ case .gAMA:
+ if gama, gama_ok := png.gamma(c); gama_ok {
+ fmt.printfln("[gAMA]: %v", gama)
+ }
+ case .pHYs:
+ if phys, phys_ok := png.phys(c); phys_ok {
+ if phys.unit == .Meter {
+ xm := f32(img.width) / f32(phys.ppu_x)
+ ym := f32(img.height) / f32(phys.ppu_y)
+ dpi_x, dpi_y := png.phys_to_dpi(phys)
+ fmt.printfln("[pHYs] Image resolution is %v x %v pixels per meter.", phys.ppu_x, phys.ppu_y)
+ fmt.printfln("[pHYs] Image resolution is %v x %v DPI.", dpi_x, dpi_y)
+ fmt.printfln("[pHYs] Image dimensions are %v x %v meters.", xm, ym)
+ } else {
+ fmt.printfln("[pHYs] x: %v, y: %v pixels per unknown unit.", phys.ppu_x, phys.ppu_y)
+ }
+ }
+ case .iTXt, .zTXt, .tEXt:
+ res, ok_text := png.text(c)
+ if ok_text {
+ if c.header.type == .iTXt {
+ fmt.printfln("[iTXt] %v (%v:%v): %v", res.keyword, res.language, res.keyword_localized, res.text)
+ } else {
+ fmt.printfln("[tEXt/zTXt] %v: %v", res.keyword, res.text)
+ }
+ }
+ defer png.text_destroy(res)
+ case .bKGD:
+ fmt.printfln("[bKGD] %v", img.background)
+ case .eXIf:
+ if res, ok_exif := png.exif(c); ok_exif {
+ /*
+ Other than checking the signature and byte order, we don't handle Exif data.
+ If you wish to interpret it, pass it to an Exif parser.
+ */
+ fmt.printfln("[eXIf] %v", res)
+ }
+ case .PLTE:
+ if plte, plte_ok := png.plte(c); plte_ok {
+ fmt.printfln("[PLTE] %v", plte)
+ } else {
+ fmt.printfln("[PLTE] Error")
+ }
+ case .hIST:
+ if res, ok_hist := png.hist(c); ok_hist {
+ fmt.printfln("[hIST] %v", res)
+ }
+ case .cHRM:
+ if res, ok_chrm := png.chrm(c); ok_chrm {
+ fmt.printfln("[cHRM] %v", res)
+ }
+ case .sPLT:
+ res, ok_splt := png.splt(c)
+ if ok_splt {
+ fmt.printfln("[sPLT] %v", res)
+ }
+ png.splt_destroy(res)
+ case .sBIT:
+ if res, ok_sbit := png.sbit(c); ok_sbit {
+ fmt.printfln("[sBIT] %v", res)
+ }
+ case .iCCP:
+ res, ok_iccp := png.iccp(c)
+ if ok_iccp {
+ fmt.printfln("[iCCP] %v", res)
+ }
+ png.iccp_destroy(res)
+ case .sRGB:
+ if res, ok_srgb := png.srgb(c); ok_srgb {
+ fmt.printfln("[sRGB] Rendering intent: %v", res)
+ }
+ case:
+ type := c.header.type
+ name := png.chunk_type_to_name(&type)
+ fmt.printfln("[%v]: %v", name, c.data)
+ }
+ }
+ }
+ }
+
+ fmt.printfln("Done parsing metadata.")
+
+ if err == nil && .do_not_decompress_image not_in options && .info not_in options {
+ if err = tga.save("out.tga", img); err == nil {
+ fmt.println("Saved decoded image.")
+ } else {
+ fmt.eprintfln("Error %v saving out.ppm.", err)
+ }
+ }
+}
+
+main :: proc() {
+ track: mem.Tracking_Allocator
+ mem.tracking_allocator_init(&track, context.allocator)
+ defer mem.tracking_allocator_destroy(&track)
+ context.allocator = mem.tracking_allocator(&track)
+
+ demo()
+
+ for _, leak in track.allocation_map {
+ fmt.printf("%v leaked %m", leak.location, leak.size)
+ }
+}
\ No newline at end of file
diff --git a/core/image/png/png.odin b/core/image/png/png.odin
index 0170d3168..5a0913cff 100644
--- a/core/image/png/png.odin
+++ b/core/image/png/png.odin
@@ -1,4 +1,6 @@
#+feature using-stmt
+// Reader for `PNG` images.
+// The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]].
package png
/*
diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin
index 8e0706206..5fc10cec4 100644
--- a/core/image/png/png_os.odin
+++ b/core/image/png/png_os.odin
@@ -8,12 +8,12 @@ load :: proc{load_from_file, load_from_bytes, load_from_context}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator
- data, ok := os.read_entire_file(filename)
- defer delete(data)
+ data, data_err := os.read_entire_file(filename, allocator)
+ defer delete(data, allocator)
- if ok {
+ if data_err == nil {
return load_from_bytes(data, options)
} else {
return nil, .Unable_To_Read_File
}
-}
+}
\ No newline at end of file
diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin
index c85fdd839..f2bf83cfc 100644
--- a/core/image/qoi/qoi_os.odin
+++ b/core/image/qoi/qoi_os.odin
@@ -4,8 +4,22 @@ package qoi
import "core:os"
import "core:bytes"
-save :: proc{save_to_buffer, save_to_file}
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+ context.allocator = allocator
+
+ data, data_err := os.read_entire_file(filename, allocator)
+ defer delete(data, allocator)
+
+ if data_err == nil {
+ return load_from_bytes(data, options)
+ } else {
+ return nil, .Unable_To_Read_File
+ }
+}
+
+save :: proc{save_to_buffer, save_to_file}
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
@@ -14,24 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
defer bytes.buffer_destroy(out)
save_to_buffer(out, img, options) or_return
- write_ok := os.write_entire_file(output, out.buf[:])
+ write_err := os.write_entire_file(output, out.buf[:])
- return nil if write_ok else .Unable_To_Write_File
-}
-
-
-load :: proc{load_from_file, load_from_bytes, load_from_context}
-
-
-load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
- context.allocator = allocator
-
- data, ok := os.read_entire_file(filename)
- defer delete(data)
-
- if ok {
- return load_from_bytes(data, options)
- } else {
- return nil, .Unable_To_Read_File
- }
+ return nil if write_err == nil else .Unable_To_Write_File
}
\ No newline at end of file
diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin
index a78998105..ba50439de 100644
--- a/core/image/tga/tga_os.odin
+++ b/core/image/tga/tga_os.odin
@@ -4,6 +4,21 @@ package tga
import "core:os"
import "core:bytes"
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+ context.allocator = allocator
+
+ data, data_err := os.read_entire_file(filename, allocator)
+ defer delete(data)
+
+ if data_err == nil {
+ return load_from_bytes(data, options)
+ } else {
+ return nil, .Unable_To_Read_File
+ }
+}
+
save :: proc{save_to_buffer, save_to_file}
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
@@ -13,22 +28,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
defer bytes.buffer_destroy(out)
save_to_buffer(out, img, options) or_return
- write_ok := os.write_entire_file(output, out.buf[:])
+ write_err := os.write_entire_file(output, out.buf[:])
- return nil if write_ok else .Unable_To_Write_File
-}
-
-load :: proc{load_from_file, load_from_bytes, load_from_context}
-
-load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
- context.allocator = allocator
-
- data, ok := os.read_entire_file(filename)
- defer delete(data)
-
- if ok {
- return load_from_bytes(data, options)
- } else {
- return nil, .Unable_To_Read_File
- }
+ return nil if write_err == nil else .Unable_To_Write_File
}
\ No newline at end of file
diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin
index f0acc8a22..47174719f 100644
--- a/core/log/file_console_logger.odin
+++ b/core/log/file_console_logger.odin
@@ -1,5 +1,6 @@
#+build !freestanding
#+build !orca
+#+build !js
package log
import "base:runtime"
@@ -35,7 +36,7 @@ Default_File_Logger_Opts :: Options{
File_Console_Logger_Data :: struct {
- file_handle: os.Handle,
+ file_handle: ^os.File,
ident: string,
}
@@ -66,16 +67,16 @@ init_standard_stream_status :: proc "contextless" () {
}
}
-create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
+create_file_logger :: proc(f: ^os.File, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator)
- data.file_handle = h
+ data.file_handle = f
data.ident = ident
return Logger{file_logger_proc, data, lowest, opt}
}
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
data := cast(^File_Console_Logger_Data)log.data
- if data.file_handle != os.INVALID_HANDLE {
+ if data.file_handle != nil {
os.close(data.file_handle)
}
free(data, allocator)
@@ -83,7 +84,7 @@ destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator)
- data.file_handle = os.INVALID_HANDLE
+ data.file_handle = nil
data.ident = ident
return Logger{console_logger_proc, data, lowest, opt}
}
@@ -93,7 +94,7 @@ destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
}
@(private)
-_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
+_file_console_logger_proc :: proc(h: ^os.File, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:])
@@ -106,9 +107,7 @@ _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, tex
do_location_header(options, &buf, location)
if .Thread_Id in options {
- // NOTE(Oskar): not using context.thread_id here since that could be
- // incorrect when replacing context for a thread.
- fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
+ fmt.sbprintf(&buf, "[{}] ", os.get_current_thread_id())
}
if ident != "" {
@@ -126,7 +125,7 @@ file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, option
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
- h: os.Handle = ---
+ h: ^os.File = nil
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
@@ -216,4 +215,4 @@ do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #ca
}
fmt.sbprint(buf, "] ")
-}
+}
\ No newline at end of file
diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin
index 9c87440fc..0d57bc071 100644
--- a/core/math/big/radix.odin
+++ b/core/math/big/radix.odin
@@ -15,9 +15,8 @@ package math_big
- Also look at extracting and splatting several digits at once.
*/
-import "base:intrinsics"
-import "core:mem"
-import "core:os"
+import "base:intrinsics"
+import "core:mem"
/*
This version of `itoa` allocates on behalf of the caller. The caller must free the string.
@@ -386,64 +385,7 @@ radix_size :: proc(a: ^Int, radix: i8, zero_terminate := false, allocator := con
return size, nil
}
-/*
- We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions.
- LibTomMath allows exporting/importing to/from a file in ASCII, but it doesn't support a much more compact representation in binary, even though it has several pack functions int_to_bytes_* (which I expanded upon and wrote Python interoperable versions of as well), and (un)pack, which is GMP compatible.
- Someone could implement their own read/write binary int procedures, of course.
-
- Could be worthwhile to add a canonical binary file representation with an optional small header that says it's an Odin big.Int, big.Rat or Big.Float, byte count for each component that follows, flag for big/little endian and a flag that says a checksum exists at the end of the file.
- For big.Rat and big.Float the header couldn't be optional, because we'd have no way to distinguish where the components end.
-*/
-
-/*
- Read an Int from an ASCII file.
-*/
-internal_int_read_from_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) {
- context.allocator = allocator
-
- /*
- We can either read the entire file at once, or read a bunch at a time and keep multiplying by the radix.
- For now, we'll read the entire file. Eventually we'll replace this with a copy that duplicates the logic
- of `atoi` so we don't need to read the entire file.
- */
-
- res, ok := os.read_entire_file(filename, allocator)
- defer delete(res, allocator)
-
- if !ok {
- return .Cannot_Read_File
- }
-
- as := string(res)
- return atoi(a, as, radix)
-}
-
-/*
- Write an Int to an ASCII file.
-*/
-internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) {
- context.allocator = allocator
-
- /*
- For now we'll convert the Int using itoa and writing the result in one go.
- If we want to preserve memory we could duplicate the itoa logic and write backwards.
- */
-
- as := itoa(a, radix) or_return
- defer delete(as)
-
- l := len(as)
- assert(l > 0)
-
- data := transmute([]u8)mem.Raw_Slice{
- data = raw_data(as),
- len = l,
- }
-
- ok := os.write_entire_file(filename, data, truncate=true)
- return nil if ok else .Cannot_Write_File
-}
/*
Calculate the size needed for `internal_int_pack`.
diff --git a/core/math/big/radix_os.odin b/core/math/big/radix_os.odin
new file mode 100644
index 000000000..50454b679
--- /dev/null
+++ b/core/math/big/radix_os.odin
@@ -0,0 +1,79 @@
+#+build !freestanding
+#+build !js
+package math_big
+
+/*
+ Copyright 2021 Jeroen van Rijn .
+ Made available under Odin's license.
+
+ An arbitrary precision mathematics implementation in Odin.
+ For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
+ The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
+
+ This file contains radix conversions, `string_to_int` (atoi) and `int_to_string` (itoa).
+
+ TODO:
+ - Use Barrett reduction for non-powers-of-two.
+ - Also look at extracting and splatting several digits at once.
+*/
+
+import "core:mem"
+import "core:os"
+
+/*
+ We might add functions to read and write byte-encoded Ints from/to files, using `int_to_bytes_*` functions.
+
+ LibTomMath allows exporting/importing to/from a file in ASCII, but it doesn't support a much more compact representation in binary, even though it has several pack functions int_to_bytes_* (which I expanded upon and wrote Python interoperable versions of as well), and (un)pack, which is GMP compatible.
+ Someone could implement their own read/write binary int procedures, of course.
+
+ Could be worthwhile to add a canonical binary file representation with an optional small header that says it's an Odin big.Int, big.Rat or Big.Float, byte count for each component that follows, flag for big/little endian and a flag that says a checksum exists at the end of the file.
+ For big.Rat and big.Float the header couldn't be optional, because we'd have no way to distinguish where the components end.
+*/
+
+/*
+ Read an Int from an ASCII file.
+*/
+internal_int_read_from_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) {
+ context.allocator = allocator
+
+ /*
+ We can either read the entire file at once, or read a bunch at a time and keep multiplying by the radix.
+ For now, we'll read the entire file. Eventually we'll replace this with a copy that duplicates the logic
+ of `atoi` so we don't need to read the entire file.
+ */
+ res, res_err := os.read_entire_file(filename, allocator)
+ defer delete(res, allocator)
+
+ if res_err != nil {
+ return .Cannot_Read_File
+ }
+
+ as := string(res)
+ return atoi(a, as, radix)
+}
+
+/*
+ Write an Int to an ASCII file.
+*/
+internal_int_write_to_ascii_file :: proc(a: ^Int, filename: string, radix := i8(10), allocator := context.allocator) -> (err: Error) {
+ context.allocator = allocator
+
+ /*
+ For now we'll convert the Int using itoa and writing the result in one go.
+ If we want to preserve memory we could duplicate the itoa logic and write backwards.
+ */
+
+ as := itoa(a, radix) or_return
+ defer delete(as)
+
+ l := len(as)
+ assert(l > 0)
+
+ data := transmute([]u8)mem.Raw_Slice{
+ data = raw_data(as),
+ len = l,
+ }
+
+ write_err := os.write_entire_file(filename, data, truncate=true)
+ return nil if write_err == nil else .Cannot_Write_File
+}
\ No newline at end of file
diff --git a/core/mem/virtual/common.odin b/core/mem/virtual/common.odin
new file mode 100644
index 000000000..ad8505c89
--- /dev/null
+++ b/core/mem/virtual/common.odin
@@ -0,0 +1,16 @@
+package mem_virtual
+
+Map_File_Error :: enum {
+ None,
+ Open_Failure,
+ Stat_Failure,
+ Negative_Size,
+ Too_Large_Size,
+ Map_Failure,
+}
+
+Map_File_Flag :: enum u32 {
+ Read,
+ Write,
+}
+Map_File_Flags :: distinct bit_set[Map_File_Flag; u32]
\ No newline at end of file
diff --git a/core/mem/virtual/doc.odin b/core/mem/virtual/doc.odin
index 614e290c3..b5f0944c7 100644
--- a/core/mem/virtual/doc.odin
+++ b/core/mem/virtual/doc.odin
@@ -1,7 +1,6 @@
/*
A platform agnostic way to reserve/commit/decommit virtual memory.
-
virtual.Arena usage
Example:
@@ -27,14 +26,14 @@ Example:
// See arena_init_buffer for an arena that does not use virtual memory,
// instead it relies on you feeding it a buffer.
- f1, f1_ok := os.read_entire_file("file1.txt", arena_alloc)
- ensure(f1_ok)
+ f1, f1_err := os.read_entire_file("file1.txt", arena_alloc)
+ ensure(f1_err == nil)
- f2, f2_ok := os.read_entire_file("file2.txt", arena_alloc)
- ensure(f2_ok)
+ f2, f2_err := os.read_entire_file("file2.txt", arena_alloc)
+ ensure(f2_err == nil)
- f3, f3_ok := os.read_entire_file("file3.txt", arena_alloc)
- ensure(f3_ok)
+ f3, f3_err := os.read_entire_file("file3.txt", arena_alloc)
+ ensure(f3_err == nil)
res := make([]string, 3, arena_alloc)
res[0] = string(f1)
@@ -56,7 +55,21 @@ Example:
vmem.arena_destroy(&arena)
}
+virtual.map_file usage
+Example:
+ // Source: https://github.com/odin-lang/examples/blob/master/arena_allocator/arena_allocator.odin
+ import "core:fmt"
+
+ // virtual package implements cross-platform file memory mapping
+ import vmem "core:mem/virtual"
+
+ main :: proc() {
+ data, err := virtual.map_file_from_path(#file, {.Read})
+ defer virtual.unmap_file(data)
+ fmt.printfln("Error: %v", err)
+ fmt.printfln("Data: %s", data)
+ }
*/
package mem_virtual
diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin
index 2f852b40c..660210bbf 100644
--- a/core/mem/virtual/file.odin
+++ b/core/mem/virtual/file.odin
@@ -1,39 +1,25 @@
+#+build !freestanding
+#+build !js
package mem_virtual
import "core:os"
-Map_File_Error :: enum {
- None,
- Open_Failure,
- Stat_Failure,
- Negative_Size,
- Too_Large_Size,
- Map_Failure,
-}
-
-Map_File_Flag :: enum u32 {
- Read,
- Write,
-}
-Map_File_Flags :: distinct bit_set[Map_File_Flag; u32]
-
map_file :: proc{
map_file_from_path,
- map_file_from_file_descriptor,
+ map_file_from_file,
}
map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
- fd, err := os.open(filename, os.O_RDWR)
+ f, err := os.open(filename, os.O_RDWR)
if err != nil {
return nil, .Open_Failure
}
- defer os.close(fd)
-
- return map_file_from_file_descriptor(uintptr(fd), flags)
+ defer os.close(f)
+ return map_file_from_file(f, flags)
}
-map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
- size, os_err := os.file_size(os.Handle(fd))
+map_file_from_file :: proc(f: ^os.File, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
+ size, os_err := os.file_size(f)
if os_err != nil {
return nil, .Stat_Failure
}
@@ -43,5 +29,12 @@ map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (da
if size != i64(int(size)) {
return nil, .Too_Large_Size
}
+ fd := os.fd(f)
return _map_file(fd, size, flags)
}
+
+unmap_file :: proc(data: []byte) {
+ if raw_data(data) != nil {
+ _unmap_file(data)
+ }
+}
\ No newline at end of file
diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin
index f819fbf86..144a8dc59 100644
--- a/core/mem/virtual/virtual_linux.odin
+++ b/core/mem/virtual/virtual_linux.odin
@@ -49,7 +49,6 @@ _platform_memory_init :: proc "contextless" () {
assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0)
}
-
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
prot: linux.Mem_Protection
if .Read in flags {
@@ -66,3 +65,7 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags)
}
return ([^]byte)(addr)[:size], nil
}
+
+_unmap_file :: proc "contextless" (data: []byte) {
+ _release(raw_data(data), uint(len(data)))
+}
\ No newline at end of file
diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin
index c6386e842..8a2e1a61d 100644
--- a/core/mem/virtual/virtual_other.odin
+++ b/core/mem/virtual/virtual_other.odin
@@ -26,8 +26,13 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
}
_platform_memory_init :: proc "contextless" () {
+
}
-_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
+_map_file :: proc "contextless" (f: any, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
return nil, .Map_Failure
}
+
+_unmap_file :: proc "contextless" (data: []byte) {
+
+}
\ No newline at end of file
diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin
index 4bb161770..6f257c385 100644
--- a/core/mem/virtual/virtual_posix.odin
+++ b/core/mem/virtual/virtual_posix.odin
@@ -47,3 +47,7 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags)
}
return ([^]byte)(addr)[:size], nil
}
+
+_unmap_file :: proc "contextless" (data: []byte) {
+ _release(raw_data(data), uint(len(data)))
+}
\ No newline at end of file
diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin
index 1d777af17..0866ebfa1 100644
--- a/core/mem/virtual/virtual_windows.odin
+++ b/core/mem/virtual/virtual_windows.odin
@@ -82,6 +82,8 @@ foreign Kernel32 {
dwFileOffsetLow: u32,
dwNumberOfBytesToMap: uint,
) -> rawptr ---
+
+ UnmapViewOfFile :: proc(lpBaseAddress: rawptr) -> b32 ---
}
@(no_sanitize_address)
@@ -185,3 +187,8 @@ _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags)
file_data := MapViewOfFile(handle, desired_access, 0, 0, uint(size))
return ([^]byte)(file_data)[:size], nil
}
+
+@(no_sanitize_address)
+_unmap_file :: proc "contextless" (data: []byte) {
+ UnmapViewOfFile(raw_data(data))
+}
\ No newline at end of file
diff --git a/core/net/dns.odin b/core/net/dns.odin
index 983f82681..6af18798b 100644
--- a/core/net/dns.odin
+++ b/core/net/dns.odin
@@ -23,7 +23,6 @@ package net
@(require)
import "base:runtime"
-
import "core:bufio"
import "core:io"
import "core:math/rand"
diff --git a/core/net/dns_os.odin b/core/net/dns_os.odin
index 19db0097a..ad9724d37 100644
--- a/core/net/dns_os.odin
+++ b/core/net/dns_os.odin
@@ -7,7 +7,8 @@ import "core:os"
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
context.allocator = allocator
- res := os.read_entire_file_from_filename(resolv_conf_path) or_return
+ res, err := os.read_entire_file(resolv_conf_path, allocator)
+ if err != nil { return }
defer delete(res)
resolv_str := string(res)
@@ -15,10 +16,9 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato
}
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
- hosts_file, err := os.open(hosts_file_path)
+ handle, err := os.open(hosts_file_path)
if err != nil { return }
- defer os.close(hosts_file)
-
- return parse_hosts(os.stream_from_handle(hosts_file), allocator)
-}
+ defer os.close(handle)
+ return parse_hosts(os.to_stream(handle), allocator)
+}
\ No newline at end of file
diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin
index d4e532ec7..2ea47ca89 100644
--- a/core/odin/parser/parse_files.odin
+++ b/core/odin/parser/parse_files.odin
@@ -11,8 +11,8 @@ import "core:strings"
collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
NO_POS :: tokenizer.Pos{}
- pkg_path, pkg_path_ok := filepath.abs(path)
- if !pkg_path_ok {
+ pkg_path, pkg_path_err := os.get_absolute_path(path, context.allocator)
+ if pkg_path_err != nil {
return
}
@@ -28,14 +28,13 @@ collect_package :: proc(path: string) -> (pkg: ^ast.Package, success: bool) {
pkg.fullpath = pkg_path
for match in matches {
- src: []byte
- fullpath, ok := filepath.abs(match)
- if !ok {
+ fullpath, fullpath_err := os.get_absolute_path(match, context.allocator)
+ if fullpath_err != nil {
return
}
- src, ok = os.read_entire_file(fullpath)
- if !ok {
+ src, src_err := os.read_entire_file(fullpath, context.allocator)
+ if src_err != nil {
delete(fullpath)
return
}
diff --git a/core/os/os2/allocators.odin b/core/os/allocators.odin
similarity index 99%
rename from core/os/os2/allocators.odin
rename to core/os/allocators.odin
index 36a7d72be..e49d416e1 100644
--- a/core/os/os2/allocators.odin
+++ b/core/os/allocators.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/dir.odin b/core/os/dir.odin
similarity index 99%
rename from core/os/os2/dir.odin
rename to core/os/dir.odin
index f63754273..a2fba81e4 100644
--- a/core/os/os2/dir.odin
+++ b/core/os/dir.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
import "core:slice"
@@ -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")
diff --git a/core/os/dir_js.odin b/core/os/dir_js.odin
new file mode 100644
index 000000000..8c45cf63b
--- /dev/null
+++ b/core/os/dir_js.odin
@@ -0,0 +1,24 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:intrinsics"
+
+Read_Directory_Iterator_Impl :: struct {
+ fullpath: [dynamic]byte,
+ buf: []byte,
+ off: int,
+}
+
+@(require_results)
+_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
+ return {}, -1, false
+}
+
+_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
+
+}
+
+_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+
+}
diff --git a/core/os/os2/dir_linux.odin b/core/os/dir_linux.odin
similarity index 99%
rename from core/os/os2/dir_linux.odin
rename to core/os/dir_linux.odin
index 34346c02f..1ca2ef9b4 100644
--- a/core/os/os2/dir_linux.odin
+++ b/core/os/dir_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:sys/linux"
diff --git a/core/os/os2/dir_posix.odin b/core/os/dir_posix.odin
similarity index 99%
rename from core/os/os2/dir_posix.odin
rename to core/os/dir_posix.odin
index d9fa16f8d..c67a37430 100644
--- a/core/os/os2/dir_posix.odin
+++ b/core/os/dir_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "core:sys/posix"
diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/dir_posix_darwin.odin
similarity index 97%
rename from core/os/os2/dir_posix_darwin.odin
rename to core/os/dir_posix_darwin.odin
index 3cae50d25..e67c917b4 100644
--- a/core/os/os2/dir_posix_darwin.odin
+++ b/core/os/dir_posix_darwin.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:sys/darwin"
diff --git a/core/os/os2/dir_walker.odin b/core/os/dir_walker.odin
similarity index 98%
rename from core/os/os2/dir_walker.odin
rename to core/os/dir_walker.odin
index 0af751f31..b510b7a2a 100644
--- a/core/os/os2/dir_walker.odin
+++ b/core/os/dir_walker.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "core:container/queue"
@@ -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")
@@ -227,4 +227,4 @@ walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
}
return info, iter_ok
-}
+}
\ No newline at end of file
diff --git a/core/os/os2/dir_wasi.odin b/core/os/dir_wasi.odin
similarity index 98%
rename from core/os/os2/dir_wasi.odin
rename to core/os/dir_wasi.odin
index 61c005674..05d74be31 100644
--- a/core/os/os2/dir_wasi.odin
+++ b/core/os/dir_wasi.odin
@@ -1,7 +1,6 @@
#+private
-package os2
+package os
-import "base:runtime"
import "core:slice"
import "base:intrinsics"
import "core:sys/wasm/wasi"
diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin
index 40f4b9e9b..1168fe18b 100644
--- a/core/os/dir_windows.odin
+++ b/core/os/dir_windows.odin
@@ -1,114 +1,144 @@
+#+private
package os
-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)
}
diff --git a/core/os/doc.odin b/core/os/doc.odin
new file mode 100644
index 000000000..effb5582c
--- /dev/null
+++ b/core/os/doc.odin
@@ -0,0 +1,6 @@
+// package os_old provides a platform-independent interface to operating system functionality.
+// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number.
+//
+// The package os_old interface is intended to be uniform across all operating systems.
+// Features not generally available appear in the system-specific packages under core:sys/*.
+package os
diff --git a/core/os/os2/env.odin b/core/os/env.odin
similarity index 99%
rename from core/os/os2/env.odin
rename to core/os/env.odin
index 310d45af1..0b8138e6b 100644
--- a/core/os/os2/env.odin
+++ b/core/os/env.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
import "core:strings"
diff --git a/core/os/env_js.odin b/core/os/env_js.odin
new file mode 100644
index 000000000..644af61bd
--- /dev/null
+++ b/core/os/env_js.odin
@@ -0,0 +1,42 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+build_env :: proc() -> (err: Error) {
+ return
+}
+
+// delete_string_if_not_original :: proc(str: string) {
+
+// }
+
+@(require_results)
+_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
+ return
+}
+
+_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
+ return "", .Unsupported
+}
+_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
+
+@(require_results)
+_set_env :: proc(key, value: string) -> (err: Error) {
+ return .Unsupported
+}
+
+@(require_results)
+_unset_env :: proc(key: string) -> bool {
+ return true
+}
+
+_clear_env :: proc() {
+
+}
+
+@(require_results)
+_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
+ return {}, .Unsupported
+}
diff --git a/core/os/os2/env_linux.odin b/core/os/env_linux.odin
similarity index 99%
rename from core/os/os2/env_linux.odin
rename to core/os/env_linux.odin
index 7855fbfed..e55c59293 100644
--- a/core/os/os2/env_linux.odin
+++ b/core/os/env_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
import "base:intrinsics"
diff --git a/core/os/os2/env_posix.odin b/core/os/env_posix.odin
similarity index 99%
rename from core/os/os2/env_posix.odin
rename to core/os/env_posix.odin
index 72a1daf18..5d46cf7fc 100644
--- a/core/os/os2/env_posix.odin
+++ b/core/os/env_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/env_wasi.odin b/core/os/env_wasi.odin
similarity index 99%
rename from core/os/os2/env_wasi.odin
rename to core/os/env_wasi.odin
index cb40667cf..03a56421d 100644
--- a/core/os/os2/env_wasi.odin
+++ b/core/os/env_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin
index ef658b0a1..dfbc5c454 100644
--- a/core/os/env_windows.odin
+++ b/core/os/env_windows.odin
@@ -1,30 +1,37 @@
+#+private
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) {
+_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.. (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
+}
+
+
diff --git a/core/os/errors.odin b/core/os/errors.odin
index fcf70ec74..a60233f09 100644
--- a/core/os/errors.odin
+++ b/core/os/errors.odin
@@ -1,13 +1,12 @@
package os
-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 ""
-}
-
-@(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
diff --git a/core/os/errors_js.odin b/core/os/errors_js.odin
new file mode 100644
index 000000000..34779807d
--- /dev/null
+++ b/core/os/errors_js.odin
@@ -0,0 +1,13 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+_Platform_Error :: enum i32 {}
+
+_error_string :: proc(errno: i32) -> string {
+ return ""
+}
+
+_get_platform_error :: proc(errno: _Platform_Error) -> Error {
+ return Platform_Error(errno)
+}
diff --git a/core/os/os2/errors_linux.odin b/core/os/errors_linux.odin
similarity index 99%
rename from core/os/os2/errors_linux.odin
rename to core/os/errors_linux.odin
index a7556c306..891b4177c 100644
--- a/core/os/os2/errors_linux.odin
+++ b/core/os/errors_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:sys/linux"
diff --git a/core/os/os2/errors_posix.odin b/core/os/errors_posix.odin
similarity index 98%
rename from core/os/os2/errors_posix.odin
rename to core/os/errors_posix.odin
index 8a9ca07df..5233fec1a 100644
--- a/core/os/os2/errors_posix.odin
+++ b/core/os/errors_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "core:sys/posix"
diff --git a/core/os/os2/errors_wasi.odin b/core/os/errors_wasi.odin
similarity index 98%
rename from core/os/os2/errors_wasi.odin
rename to core/os/errors_wasi.odin
index b88e5b81e..a0377ce96 100644
--- a/core/os/os2/errors_wasi.odin
+++ b/core/os/errors_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/errors_windows.odin b/core/os/errors_windows.odin
similarity index 99%
rename from core/os/os2/errors_windows.odin
rename to core/os/errors_windows.odin
index 404560f98..639780337 100644
--- a/core/os/os2/errors_windows.odin
+++ b/core/os/errors_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
import "core:slice"
diff --git a/core/os/os2/file.odin b/core/os/file.odin
similarity index 99%
rename from core/os/os2/file.odin
rename to core/os/file.odin
index 33446726e..61582c528 100644
--- a/core/os/os2/file.odin
+++ b/core/os/file.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "core:io"
import "core:time"
@@ -538,6 +538,11 @@ is_directory :: proc(path: string) -> bool {
/*
`copy_file` copies a file from `src_path` to `dst_path` and returns an error if any was encountered.
*/
+@(require_results)
+is_tty :: proc "contextless" (f: ^File) -> bool {
+ return _is_tty(f)
+}
+
copy_file :: proc(dst_path, src_path: string) -> Error {
when #defined(_copy_file_native) {
return _copy_file_native(dst_path, src_path)
@@ -562,4 +567,4 @@ _copy_file :: proc(dst_path, src_path: string) -> Error {
_, err := io.copy(to_writer(dst), to_reader(src))
return err
-}
+}
\ No newline at end of file
diff --git a/core/os/file_js.odin b/core/os/file_js.odin
new file mode 100644
index 000000000..5d921c9b0
--- /dev/null
+++ b/core/os/file_js.odin
@@ -0,0 +1,110 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+import "core:io"
+import "core:time"
+
+File_Impl :: distinct rawptr
+
+_open :: proc(name: string, flags: File_Flags, perm: Permissions) -> (f: ^File, err: Error) {
+ return nil, .Unsupported
+}
+
+_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) {
+ return nil, .Unsupported
+}
+
+_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
+ return nil, .Unsupported
+}
+
+_close :: proc(f: ^File_Impl) -> (err: Error) {
+ return .Unsupported
+}
+
+_fd :: proc(f: ^File) -> uintptr {
+ return 0
+}
+
+_is_tty :: proc "contextless" (f: ^File) -> bool {
+ return true
+}
+
+_name :: proc(f: ^File) -> string {
+ return ""
+}
+
+_sync :: proc(f: ^File) -> Error {
+ return .Unsupported
+}
+
+_truncate :: proc(f: ^File, size: i64) -> Error {
+ return .Unsupported
+}
+
+_remove :: proc(name: string) -> Error {
+ return .Unsupported
+}
+
+_rename :: proc(old_path, new_path: string) -> Error {
+ return .Unsupported
+}
+
+_link :: proc(old_name, new_name: string) -> Error {
+ return .Unsupported
+}
+
+_symlink :: proc(old_name, new_name: string) -> Error {
+ return .Unsupported
+}
+
+_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
+ return "", .Unsupported
+}
+
+_chdir :: proc(name: string) -> Error {
+ return .Unsupported
+}
+
+_fchdir :: proc(f: ^File) -> Error {
+ return .Unsupported
+}
+
+_fchmod :: proc(f: ^File, mode: Permissions) -> Error {
+ return .Unsupported
+}
+
+_chmod :: proc(name: string, mode: Permissions) -> Error {
+ return .Unsupported
+}
+
+_fchown :: proc(f: ^File, uid, gid: int) -> Error {
+ return .Unsupported
+}
+
+_chown :: proc(name: string, uid, gid: int) -> Error {
+ return .Unsupported
+}
+
+_lchown :: proc(name: string, uid, gid: int) -> Error {
+ return .Unsupported
+}
+
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+ return .Unsupported
+}
+
+_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
+ return .Unsupported
+}
+
+_exists :: proc(path: string) -> bool {
+ return false
+}
+
+_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
+ return 0, .Empty
+}
\ No newline at end of file
diff --git a/core/os/os2/file_linux.odin b/core/os/file_linux.odin
similarity index 97%
rename from core/os/os2/file_linux.odin
rename to core/os/file_linux.odin
index 924251dfc..d4223ba5d 100644
--- a/core/os/os2/file_linux.odin
+++ b/core/os/file_linux.odin
@@ -1,11 +1,12 @@
#+private
-package os2
+package os
import "base:runtime"
import "core:io"
import "core:time"
import "core:sync"
import "core:sys/linux"
+import "core:sys/posix"
// Most implementations will EINVAL at some point when doing big writes.
// In practice a read/write call would probably never read/write these big buffers all at once,
@@ -174,6 +175,17 @@ _fd :: proc(f: ^File) -> uintptr {
return uintptr(impl.fd)
}
+_is_tty :: proc "contextless" (f: ^File) -> bool {
+ if f == nil || f.impl == nil {
+ return false
+ }
+ impl := (^File_Impl)(f.impl)
+
+ // TODO: Replace `posix.isatty` with `tcgetattr(fd, &termios) == 0`
+ is_tty := posix.isatty(posix.FD(impl.fd))
+ return bool(is_tty)
+}
+
_name :: proc(f: ^File) -> string {
return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else ""
}
diff --git a/core/os/os2/file_posix.odin b/core/os/file_posix.odin
similarity index 98%
rename from core/os/os2/file_posix.odin
rename to core/os/file_posix.odin
index cdc8e491a..006f25e11 100644
--- a/core/os/os2/file_posix.odin
+++ b/core/os/file_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
@@ -161,6 +161,13 @@ __fd :: proc(f: ^File) -> posix.FD {
return -1
}
+_is_tty :: proc "contextless" (f: ^File) -> bool {
+ context = runtime.default_context()
+ fd := _fd(f)
+ is_tty := posix.isatty(posix.FD(fd))
+ return bool(is_tty)
+}
+
_name :: proc(f: ^File) -> string {
if f != nil && f.impl != nil {
return (^File_Impl)(f.impl).name
diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/file_posix_darwin.odin
similarity index 98%
rename from core/os/os2/file_posix_darwin.odin
rename to core/os/file_posix_darwin.odin
index aed3e56f5..5522124ae 100644
--- a/core/os/os2/file_posix_darwin.odin
+++ b/core/os/file_posix_darwin.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
@@ -43,4 +43,4 @@ _copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
}
return
-}
+}
\ No newline at end of file
diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/file_posix_freebsd.odin
similarity index 99%
rename from core/os/os2/file_posix_freebsd.odin
rename to core/os/file_posix_freebsd.odin
index 05d031930..4ed9ecc1e 100644
--- a/core/os/os2/file_posix_freebsd.odin
+++ b/core/os/file_posix_freebsd.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/file_posix_netbsd.odin
similarity index 97%
rename from core/os/os2/file_posix_netbsd.odin
rename to core/os/file_posix_netbsd.odin
index f96c227ba..791836c00 100644
--- a/core/os/os2/file_posix_netbsd.odin
+++ b/core/os/file_posix_netbsd.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/file_posix_other.odin b/core/os/file_posix_other.odin
similarity index 97%
rename from core/os/os2/file_posix_other.odin
rename to core/os/file_posix_other.odin
index 8871a0062..6430c9fb6 100644
--- a/core/os/os2/file_posix_other.odin
+++ b/core/os/file_posix_other.odin
@@ -1,6 +1,6 @@
#+private
#+build openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/file_stream.odin b/core/os/file_stream.odin
similarity index 99%
rename from core/os/os2/file_stream.odin
rename to core/os/file_stream.odin
index af6e50921..cee1bef47 100644
--- a/core/os/os2/file_stream.odin
+++ b/core/os/file_stream.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:intrinsics"
import "base:runtime"
diff --git a/core/os/os2/file_util.odin b/core/os/file_util.odin
similarity index 98%
rename from core/os/os2/file_util.odin
rename to core/os/file_util.odin
index c2cf7c121..505432338 100644
--- a/core/os/os2/file_util.odin
+++ b/core/os/file_util.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
import "core:strconv"
@@ -156,9 +156,12 @@ read_entire_file :: proc{
*/
@(require_results)
read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator, loc := #caller_location) -> (data: []byte, err: Error) {
- f := open(name) or_return
+ f, ferr := open(name)
+ if ferr != nil {
+ return nil, ferr
+ }
defer close(f)
- return read_entire_file_from_file(f, allocator, loc)
+ return read_entire_file_from_file(f=f, allocator=allocator, loc=loc)
}
/*
diff --git a/core/os/os2/file_wasi.odin b/core/os/file_wasi.odin
similarity index 97%
rename from core/os/os2/file_wasi.odin
rename to core/os/file_wasi.odin
index fc37ef9f2..116c237d6 100644
--- a/core/os/os2/file_wasi.odin
+++ b/core/os/file_wasi.odin
@@ -1,5 +1,6 @@
+#+feature global-context
#+private
-package os2
+package os
import "base:runtime"
@@ -40,7 +41,6 @@ init_std_files :: proc "contextless" () {
data = impl,
procedure = _file_stream_proc,
}
- impl.file.fstat = _fstat
return &impl.file
}
@@ -221,7 +221,6 @@ _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -
data = impl,
procedure = _file_stream_proc,
}
- impl.file.fstat = _fstat
return &impl.file, nil
}
@@ -273,6 +272,10 @@ __fd :: proc(f: ^File) -> wasi.fd_t {
return -1
}
+_is_tty :: proc "contextless" (f: ^File) -> bool {
+ return false
+}
+
_name :: proc(f: ^File) -> string {
if f != nil && f.impl != nil {
return (^File_Impl)(f.impl).name
@@ -429,7 +432,7 @@ _exists :: proc(path: string) -> bool {
return true
}
-_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
+_file_stream_proc :: proc(stream_data: rawptr, mode: File_Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From, allocator: runtime.Allocator) -> (n: i64, err: Error) {
f := (^File_Impl)(stream_data)
fd := f.fd
@@ -557,6 +560,10 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte,
case .Query:
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query})
+ case .Fstat:
+ err = file_stream_fstat_utility(f, p, allocator)
+ return
+
case:
return 0, .Unsupported
}
diff --git a/core/os/os2/file_windows.odin b/core/os/file_windows.odin
similarity index 99%
rename from core/os/os2/file_windows.odin
rename to core/os/file_windows.odin
index 6f29d151c..3a9e90fed 100644
--- a/core/os/os2/file_windows.odin
+++ b/core/os/file_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
@@ -246,6 +246,11 @@ _fd :: proc "contextless" (f: ^File) -> uintptr {
return uintptr((^File_Impl)(f.impl).fd)
}
+_is_tty :: proc "contextless" (f: ^File) -> bool {
+ fd := _fd(f)
+ return win32.GetFileType(win32.HANDLE(fd)) == win32.FILE_TYPE_CHAR
+}
+
_destroy :: proc(f: ^File_Impl) -> Error {
if f == nil {
return nil
diff --git a/core/os/os2/heap.odin b/core/os/heap.odin
similarity index 98%
rename from core/os/os2/heap.odin
rename to core/os/heap.odin
index b1db54dc7..356e60b4d 100644
--- a/core/os/os2/heap.odin
+++ b/core/os/heap.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/heap_js.odin b/core/os/heap_js.odin
new file mode 100644
index 000000000..5f8d9bd35
--- /dev/null
+++ b/core/os/heap_js.odin
@@ -0,0 +1,7 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+_heap_allocator_proc :: runtime.wasm_allocator_proc
diff --git a/core/os/os2/heap_linux.odin b/core/os/heap_linux.odin
similarity index 87%
rename from core/os/os2/heap_linux.odin
rename to core/os/heap_linux.odin
index 1d1f12726..69c2b6edf 100644
--- a/core/os/os2/heap_linux.odin
+++ b/core/os/heap_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/heap_posix.odin b/core/os/heap_posix.odin
similarity index 91%
rename from core/os/os2/heap_posix.odin
rename to core/os/heap_posix.odin
index 1b52aed75..5bff96de6 100644
--- a/core/os/os2/heap_posix.odin
+++ b/core/os/heap_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/heap_wasi.odin b/core/os/heap_wasi.odin
similarity index 87%
rename from core/os/os2/heap_wasi.odin
rename to core/os/heap_wasi.odin
index 7da3c4845..066a43f85 100644
--- a/core/os/os2/heap_wasi.odin
+++ b/core/os/heap_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/heap_windows.odin b/core/os/heap_windows.odin
similarity index 99%
rename from core/os/os2/heap_windows.odin
rename to core/os/heap_windows.odin
index 7fd4529a0..e0e62a91f 100644
--- a/core/os/os2/heap_windows.odin
+++ b/core/os/heap_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:mem"
import win32 "core:sys/windows"
diff --git a/core/os/os2/internal_util.odin b/core/os/internal_util.odin
similarity index 99%
rename from core/os/os2/internal_util.odin
rename to core/os/internal_util.odin
index 9616af8b0..a279e9bee 100644
--- a/core/os/os2/internal_util.odin
+++ b/core/os/internal_util.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:intrinsics"
import "base:runtime"
diff --git a/core/os/dir_unix.odin b/core/os/old/dir_unix.odin
similarity index 98%
rename from core/os/dir_unix.odin
rename to core/os/old/dir_unix.odin
index c3dd844ef..95cc887d6 100644
--- a/core/os/dir_unix.odin
+++ b/core/os/old/dir_unix.odin
@@ -1,5 +1,5 @@
#+build darwin, linux, netbsd, freebsd, openbsd, haiku
-package os
+package os_old
import "core:strings"
diff --git a/core/os/old/dir_windows.odin b/core/os/old/dir_windows.odin
new file mode 100644
index 000000000..b81787872
--- /dev/null
+++ b/core/os/old/dir_windows.odin
@@ -0,0 +1,114 @@
+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
+}
diff --git a/core/os/old/env_windows.odin b/core/os/old/env_windows.odin
new file mode 100644
index 000000000..f9480340c
--- /dev/null
+++ b/core/os/old/env_windows.odin
@@ -0,0 +1,140 @@
+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.. (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 ""
+}
+
+@(private, require_results)
+error_to_io_error :: proc(ferr: Error) -> io.Error {
+ if ferr == nil {
+ return .None
+ }
+ return ferr.(io.Error) or_else .Unknown
+}
diff --git a/core/os/os.odin b/core/os/old/os.odin
similarity index 99%
rename from core/os/os.odin
rename to core/os/old/os.odin
index da7b0c151..01e93126d 100644
--- a/core/os/os.odin
+++ b/core/os/old/os.odin
@@ -1,5 +1,5 @@
// Cross-platform `OS` interactions like file `I/O`.
-package os
+package os_old
import "base:intrinsics"
import "base:runtime"
diff --git a/core/os/os_darwin.odin b/core/os/old/os_darwin.odin
similarity index 99%
rename from core/os/os_darwin.odin
rename to core/os/old/os_darwin.odin
index d8f7a9577..6a6efe4e2 100644
--- a/core/os/os_darwin.odin
+++ b/core/os/old/os_darwin.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import dl "system:dl"
foreign import libc "system:System"
@@ -598,7 +598,7 @@ foreign libc {
@(link_name="fstat64") _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="fsync") _unix_fsync :: proc(handle: Handle) -> c.int ---
+ @(link_name="fsync") _unix_fsync :: proc(handle: Handle) -> c.int ---
@(link_name="dup") _unix_dup :: proc(handle: Handle) -> Handle ---
@(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir ---
diff --git a/core/os/os_essence.odin b/core/os/old/os_essence.odin
similarity index 98%
rename from core/os/os_essence.odin
rename to core/os/old/os_essence.odin
index 75c4c1156..8156bb496 100644
--- a/core/os/os_essence.odin
+++ b/core/os/old/os_essence.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
import "core:sys/es"
diff --git a/core/os/os_freebsd.odin b/core/os/old/os_freebsd.odin
similarity index 99%
rename from core/os/os_freebsd.odin
rename to core/os/old/os_freebsd.odin
index 82b5a2f0f..a1ecf2aff 100644
--- a/core/os/os_freebsd.odin
+++ b/core/os/old/os_freebsd.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import dl "system:dl"
foreign import libc "system:c"
diff --git a/core/os/old/os_freestanding.odin b/core/os/old/os_freestanding.odin
new file mode 100644
index 000000000..def000aae
--- /dev/null
+++ b/core/os/old/os_freestanding.odin
@@ -0,0 +1,4 @@
+#+build freestanding
+package os_old
+
+#panic("package os_old does not support a freestanding target")
diff --git a/core/os/os_haiku.odin b/core/os/old/os_haiku.odin
similarity index 99%
rename from core/os/os_haiku.odin
rename to core/os/old/os_haiku.odin
index ad984e33c..a85f2d5c1 100644
--- a/core/os/os_haiku.odin
+++ b/core/os/old/os_haiku.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import lib "system:c"
diff --git a/core/os/os_js.odin b/core/os/old/os_js.odin
similarity index 99%
rename from core/os/os_js.odin
rename to core/os/old/os_js.odin
index 1870218d3..cefabbf4d 100644
--- a/core/os/os_js.odin
+++ b/core/os/old/os_js.odin
@@ -1,5 +1,5 @@
#+build js
-package os
+package os_old
foreign import "odin_env"
diff --git a/core/os/os_linux.odin b/core/os/old/os_linux.odin
similarity index 98%
rename from core/os/os_linux.odin
rename to core/os/old/os_linux.odin
index 4c32676c6..504f6f5b3 100644
--- a/core/os/os_linux.odin
+++ b/core/os/old/os_linux.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import dl "system:dl"
foreign import libc "system:c"
@@ -7,19 +7,8 @@ import "base:runtime"
import "core:strings"
import "core:c"
import "core:strconv"
-
-// NOTE(flysand): For compatibility we'll make core:os package
-// depend on the old (scheduled for removal) linux package.
-// Seeing that there are plans for os2, I'm imagining that *that*
-// package should inherit the new sys functionality.
-// The reasons for these are as follows:
-// 1. It's very hard to update this package without breaking *a lot* of code.
-// 2. os2 is not stable anyways, so we can break compatibility all we want
-// It might be weird to bring up compatibility when Odin in it's nature isn't
-// all that about compatibility. But we don't want to push experimental changes
-// and have people's code break while it's still work in progress.
-import unix "core:sys/unix"
-import linux "core:sys/linux"
+import "core:sys/unix"
+import "core:sys/linux"
Handle :: distinct i32
Pid :: distinct i32
diff --git a/core/os/os_netbsd.odin b/core/os/old/os_netbsd.odin
similarity index 99%
rename from core/os/os_netbsd.odin
rename to core/os/old/os_netbsd.odin
index 640ea46cd..601e42199 100644
--- a/core/os/os_netbsd.odin
+++ b/core/os/old/os_netbsd.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import dl "system:dl"
foreign import libc "system:c"
diff --git a/core/os/os_openbsd.odin b/core/os/old/os_openbsd.odin
similarity index 99%
rename from core/os/os_openbsd.odin
rename to core/os/old/os_openbsd.odin
index bf89a21f4..95d431134 100644
--- a/core/os/os_openbsd.odin
+++ b/core/os/old/os_openbsd.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
foreign import libc "system:c"
diff --git a/core/os/os_wasi.odin b/core/os/old/os_wasi.odin
similarity index 99%
rename from core/os/os_wasi.odin
rename to core/os/old/os_wasi.odin
index fe0a1fb3e..287034957 100644
--- a/core/os/os_wasi.odin
+++ b/core/os/old/os_wasi.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
import "core:sys/wasm/wasi"
import "base:runtime"
diff --git a/core/os/os_windows.odin b/core/os/old/os_windows.odin
similarity index 99%
rename from core/os/os_windows.odin
rename to core/os/old/os_windows.odin
index cb7e42f67..8081d9726 100644
--- a/core/os/os_windows.odin
+++ b/core/os/old/os_windows.odin
@@ -1,5 +1,5 @@
#+build windows
-package os
+package os_old
import win32 "core:sys/windows"
import "base:runtime"
diff --git a/core/os/old/stat.odin b/core/os/old/stat.odin
new file mode 100644
index 000000000..fad8ff755
--- /dev/null
+++ b/core/os/old/stat.odin
@@ -0,0 +1,33 @@
+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)
diff --git a/core/os/stat_unix.odin b/core/os/old/stat_unix.odin
similarity index 99%
rename from core/os/stat_unix.odin
rename to core/os/old/stat_unix.odin
index 648987a07..0f7be62e2 100644
--- a/core/os/stat_unix.odin
+++ b/core/os/old/stat_unix.odin
@@ -1,5 +1,5 @@
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
-package os
+package os_old
import "core:time"
diff --git a/core/os/old/stat_windows.odin b/core/os/old/stat_windows.odin
new file mode 100644
index 000000000..34e5e1695
--- /dev/null
+++ b/core/os/old/stat_windows.odin
@@ -0,0 +1,303 @@
+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
+}
diff --git a/core/os/stream.odin b/core/os/old/stream.odin
similarity index 98%
rename from core/os/stream.odin
rename to core/os/old/stream.odin
index f4e9bcdde..d94505505 100644
--- a/core/os/stream.odin
+++ b/core/os/old/stream.odin
@@ -1,4 +1,4 @@
-package os
+package os_old
import "core:io"
diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin
deleted file mode 100644
index a4dadca75..000000000
--- a/core/os/os2/dir_windows.odin
+++ /dev/null
@@ -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)
-}
diff --git a/core/os/os2/doc.odin b/core/os/os2/doc.odin
deleted file mode 100644
index 86dcbc452..000000000
--- a/core/os/os2/doc.odin
+++ /dev/null
@@ -1,11 +0,0 @@
-// Package os provides a platform-independent interface to operating system functionality.
-// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number.
-//
-// The package os interface is intended to be uniform across all operating systems.
-// Features not generally available appear in the system-specific packages under core:sys/*.
-//
-//
-// IMPORTANT NOTE from Bill: This package is not fully complete yet but should give designers a better idea of the general
-// interface and how to write things. This entire interface is subject to change, but mostly working still.
-// When things are finalized, this message will be removed.
-package os2
diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin
deleted file mode 100644
index d389f8860..000000000
--- a/core/os/os2/env_windows.odin
+++ /dev/null
@@ -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.. (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
-}
-
-
diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin
deleted file mode 100644
index 2d959e182..000000000
--- a/core/os/os2/errors.odin
+++ /dev/null
@@ -1,145 +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,
-
- 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 .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
-}
diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin
deleted file mode 100644
index 58df45754..000000000
--- a/core/os/os2/stat.odin
+++ /dev/null
@@ -1,113 +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
-}
diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin
deleted file mode 100644
index 651029ac3..000000000
--- a/core/os/os2/stat_windows.odin
+++ /dev/null
@@ -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.. 0 {
- slash_count += 1
-
- if slash_count == 2 {
- return i
- }
- }
- }
-
- return len(path)
-}
\ No newline at end of file
diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin
deleted file mode 100644
index c22a6d7d5..000000000
--- a/core/os/os_freestanding.odin
+++ /dev/null
@@ -1,4 +0,0 @@
-#+build freestanding
-package os
-
-#panic("package os does not support a freestanding target")
diff --git a/core/os/os2/path.odin b/core/os/path.odin
similarity index 51%
rename from core/os/os2/path.odin
rename to core/os/path.odin
index e12aa3c9c..e0353e43d 100644
--- a/core/os/os2/path.odin
+++ b/core/os/path.odin
@@ -1,11 +1,14 @@
-package os2
+package os
import "base:runtime"
-
+import "core:slice"
import "core:strings"
+import "core:unicode/utf8"
+
Path_Separator :: _Path_Separator // OS-Specific
Path_Separator_String :: _Path_Separator_String // OS-Specific
+Path_Separator_Chars :: `/\`
Path_List_Separator :: _Path_List_Separator // OS-Specific
#assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.")
@@ -19,6 +22,34 @@ is_path_separator :: proc(c: byte) -> bool {
return _is_path_separator(c)
}
+/*
+Returns the result of replacing each path separator character in the path
+with the `new_sep` rune.
+
+*Allocates Using Provided Allocator*
+*/
+replace_path_separators :: proc(path: string, new_sep: rune, allocator: runtime.Allocator) -> (new_path: string, err: Error) {
+ buf := make([]u8, len(path), allocator) or_return
+
+ i: int
+ for r in path {
+ replacement := r
+ if r == '/' || r == '\\' {
+ replacement = new_sep
+ }
+
+ if replacement <= rune(0x7F) {
+ buf[i] = u8(replacement)
+ i += 1
+ } else {
+ b, w := utf8.encode_rune(r)
+ copy(buf[i:], b[:w])
+ i += w
+ }
+ }
+ return string(buf), nil
+}
+
mkdir :: make_directory
/*
@@ -315,6 +346,143 @@ split_path :: proc(path: string) -> (dir, filename: string) {
return _split_path(path)
}
+
+/*
+Gets the file name and extension from a path.
+
+e.g.
+ 'path/to/name.tar.gz' -> 'name.tar.gz'
+ 'path/to/name.txt' -> 'name.txt'
+ 'path/to/name' -> 'name'
+
+Returns "." if the path is an empty string.
+*/
+base :: proc(path: string) -> string {
+ if path == "" {
+ return "."
+ }
+
+ _, file := split_path(path)
+ return file
+}
+
+/*
+Gets the name of a file from a path.
+
+The stem of a file is such that `stem(path)` + `ext(path)` = `base(path)`.
+
+Only the last dot is considered when splitting the file extension.
+See `short_stem`.
+
+e.g.
+ 'name.tar.gz' -> 'name.tar'
+ 'name.txt' -> 'name'
+
+Returns an empty string if there is no stem. e.g: '.gitignore'.
+Returns an empty string if there's a trailing path separator.
+*/
+stem :: proc(path: string) -> string {
+ if len(path) > 0 {
+ if is_path_separator(path[len(path) - 1]) {
+ // NOTE(tetra): Trailing separator
+ return ""
+ } else if path[0] == '.' {
+ return ""
+ }
+ }
+
+ // NOTE(tetra): Get the basename
+ path := path
+ if i := strings.last_index_any(path, Path_Separator_Chars); i != -1 {
+ path = path[i+1:]
+ }
+
+ if i := strings.last_index_byte(path, '.'); i != -1 {
+ return path[:i]
+ }
+ return path
+}
+
+/*
+Gets the name of a file from a path.
+
+The short stem is such that `short_stem(path)` + `long_ext(path)` = `base(path)`,
+where `long_ext` is the extension returned by `split_filename_all`.
+
+The first dot is used to split off the file extension, unlike `stem` which uses the last dot.
+
+e.g.
+ 'name.tar.gz' -> 'name'
+ 'name.txt' -> 'name'
+
+Returns an empty string if there is no stem. e.g: '.gitignore'.
+Returns an empty string if there's a trailing path separator.
+*/
+short_stem :: proc(path: string) -> string {
+ s := stem(path)
+ if i := strings.index_byte(s, '.'); i != -1 {
+ return s[:i]
+ }
+ return s
+}
+
+/*
+Gets the file extension from a path, including the dot.
+
+The file extension is such that `stem_path(path)` + `ext(path)` = `base(path)`.
+
+Only the last dot is considered when splitting the file extension.
+See `long_ext`.
+
+e.g.
+ 'name.tar.gz' -> '.gz'
+ 'name.txt' -> '.txt'
+
+Returns an empty string if there is no dot.
+Returns an empty string if there is a trailing path separator.
+*/
+ext :: proc(path: string) -> string {
+ for i := len(path)-1; i >= 0 && !is_path_separator(path[i]); i -= 1 {
+ if path[i] == '.' {
+ return path[i:]
+ }
+ }
+ return ""
+}
+
+/*
+Gets the file extension from a path, including the dot.
+
+The long file extension is such that `short_stem(path)` + `long_ext(path)` = `base(path)`.
+
+The first dot is used to split off the file extension, unlike `ext` which uses the last dot.
+
+e.g.
+ 'name.tar.gz' -> '.tar.gz'
+ 'name.txt' -> '.txt'
+
+Returns an empty string if there is no dot.
+Returns an empty string if there is a trailing path separator.
+*/
+long_ext :: proc(path: string) -> string {
+ if len(path) > 0 && is_path_separator(path[len(path) - 1]) {
+ // NOTE(tetra): Trailing separator
+ return ""
+ }
+
+ // NOTE(tetra): Get the basename
+ path := path
+ if i := strings.last_index_any(path, Path_Separator_Chars); i != -1 {
+ path = path[i+1:]
+ }
+
+ if i := strings.index_byte(path, '.'); i != -1 {
+ return path[i:]
+ }
+
+ return ""
+}
+
/*
Join all `elems` with the system's path separator and normalize the result.
@@ -460,3 +628,353 @@ split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []
return list, nil
}
+
+/*
+`match` states whether "name" matches the shell pattern
+
+Pattern syntax is:
+ pattern:
+ {term}
+ term:
+ '*' matches any sequence of non-/ characters
+ '?' matches any single non-/ character
+ '[' ['^'] { character-range } ']'
+ character classification (cannot be empty)
+ c matches character c (c != '*', '?', '\\', '[')
+ '\\' c matches character c
+
+ character-range
+ c matches character c (c != '\\', '-', ']')
+ '\\' c matches character c
+ lo '-' hi matches character c for lo <= c <= hi
+
+`match` requires that the pattern matches the entirety of the name, not just a substring.
+The only possible error returned is `.Syntax_Error` or an allocation error.
+
+NOTE(bill): This is effectively the shell pattern matching system found
+*/
+match :: proc(pattern, name: string) -> (matched: bool, err: Error) {
+ pattern, name := pattern, name
+ pattern_loop: for len(pattern) > 0 {
+ star: bool
+ chunk: string
+ star, chunk, pattern = scan_chunk(pattern)
+ if star && chunk == "" {
+ return !strings.contains(name, _Path_Separator_String), nil
+ }
+
+ t, ok := match_chunk(chunk, name) or_return
+
+ if ok && (len(t) == 0 || len(pattern) > 0) {
+ name = t
+ continue
+ }
+
+ if star {
+ for i := 0; i < len(name) && name[i] != _Path_Separator; i += 1 {
+ t, ok = match_chunk(chunk, name[i+1:]) or_return
+ if ok {
+ if len(pattern) == 0 && len(t) > 0 {
+ continue
+ }
+ name = t
+ continue pattern_loop
+ }
+ }
+ }
+
+ return false, nil
+ }
+
+ return len(name) == 0, nil
+}
+
+// glob returns the names of all files matching pattern or nil if there are no matching files
+// The syntax of patterns is the same as "match".
+// The pattern may describe hierarchical names such as /usr/*/bin (assuming '/' is a separator)
+//
+// glob ignores file system errors
+//
+glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Error) {
+ _split :: proc(path: string) -> (dir, file: string) {
+ vol := volume_name(path)
+ i := len(path) - 1
+ for i >= len(vol) && !is_path_separator(path[i]) {
+ i -= 1
+ }
+ return path[:i+1], path[i+1:]
+ }
+
+ context.allocator = allocator
+
+ if !has_meta(pattern) {
+ // TODO(bill): os.lstat on here to check for error
+ m := make([]string, 1)
+ m[0] = pattern
+ return m[:], nil
+ }
+
+ // NOTE(Jeroen): For `glob`, we need this version of `split`, which leaves the trailing `/` on `dir`.
+ dir, file := _split(pattern)
+
+ temp_buf: [8]byte
+ vol_len: int
+ vol_len, dir = clean_glob_path(dir, temp_buf[:])
+
+ if !has_meta(dir[vol_len:]) {
+ m, e := _glob(dir, file, nil)
+ return m[:], e
+ }
+
+ m := glob(dir) or_return
+ defer {
+ for s in m {
+ delete(s)
+ }
+ delete(m)
+ }
+
+ dmatches := make([dynamic]string, 0, 0)
+ for d in m {
+ dmatches, err = _glob(d, file, &dmatches)
+ if err != nil {
+ break
+ }
+ }
+ if len(dmatches) > 0 {
+ matches = dmatches[:]
+ }
+ return
+}
+
+/*
+ Returns leading volume name.
+
+ e.g.
+ "C:\foo\bar\baz" will return "C:" on Windows.
+ Everything else will be "".
+*/
+volume_name :: proc(path: string) -> string {
+ when ODIN_OS == .Windows {
+ return path[:_volume_name_len(path)]
+ } else {
+ return ""
+ }
+}
+
+@(private="file")
+scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) {
+ pattern := pattern
+ for len(pattern) > 0 && pattern[0] == '*' {
+ pattern = pattern[1:]
+ star = true
+ }
+
+ in_range, i := false, 0
+
+ scan_loop: for i = 0; i < len(pattern); i += 1 {
+ switch pattern[i] {
+ case '\\':
+ when ODIN_OS != .Windows {
+ if i+1 < len(pattern) {
+ i += 1
+ }
+ }
+ case '[':
+ in_range = true
+ case ']':
+ in_range = false
+ case '*':
+ in_range or_break scan_loop
+
+ }
+ }
+ return star, pattern[:i], pattern[i:]
+}
+
+@(private="file")
+match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Error) {
+ slash_equal :: proc(a, b: u8) -> bool {
+ switch a {
+ case '/': return b == '/' || b == '\\'
+ case '\\': return b == '/' || b == '\\'
+ case: return a == b
+ }
+ }
+
+ chunk, s := chunk, s
+ for len(chunk) > 0 {
+ if len(s) == 0 {
+ return
+ }
+ switch chunk[0] {
+ case '[':
+ r, w := utf8.decode_rune_in_string(s)
+ s = s[w:]
+ chunk = chunk[1:]
+ is_negated := false
+ if len(chunk) > 0 && chunk[0] == '^' {
+ is_negated = true
+ chunk = chunk[1:]
+ }
+ match := false
+ range_count := 0
+ for {
+ if len(chunk) > 0 && chunk[0] == ']' && range_count > 0 {
+ chunk = chunk[1:]
+ break
+ }
+ lo, hi: rune
+ if lo, chunk, err = get_escape(chunk); err != nil {
+ return
+ }
+ hi = lo
+ if chunk[0] == '-' {
+ if hi, chunk, err = get_escape(chunk[1:]); err != nil {
+ return
+ }
+ }
+
+ if lo <= r && r <= hi {
+ match = true
+ }
+ range_count += 1
+ }
+ if match == is_negated {
+ return
+ }
+
+ case '?':
+ if s[0] == _Path_Separator {
+ return
+ }
+ _, w := utf8.decode_rune_in_string(s)
+ s = s[w:]
+ chunk = chunk[1:]
+
+ case '\\':
+ when ODIN_OS != .Windows {
+ chunk = chunk[1:]
+ if len(chunk) == 0 {
+ err = .Pattern_Syntax_Error
+ return
+ }
+ }
+ fallthrough
+ case:
+ if !slash_equal(chunk[0], s[0]) {
+ return
+ }
+ s = s[1:]
+ chunk = chunk[1:]
+
+ }
+ }
+ return s, true, nil
+}
+
+@(private="file")
+get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Error) {
+ if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
+ err = .Pattern_Syntax_Error
+ return
+ }
+ chunk := chunk
+ if chunk[0] == '\\' && ODIN_OS != .Windows {
+ chunk = chunk[1:]
+ if len(chunk) == 0 {
+ err = .Pattern_Syntax_Error
+ return
+ }
+ }
+
+ w: int
+ r, w = utf8.decode_rune_in_string(chunk)
+ if r == utf8.RUNE_ERROR && w == 1 {
+ err = .Pattern_Syntax_Error
+ }
+
+ next_chunk = chunk[w:]
+ if len(next_chunk) == 0 {
+ err = .Pattern_Syntax_Error
+ }
+
+ return
+}
+
+// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`.
+_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Error) {
+ context.allocator = allocator
+
+ if matches != nil {
+ m = matches^
+ } else {
+ m = make([dynamic]string, 0, 0)
+ }
+
+
+ d := open(dir, O_RDONLY) or_return
+ defer close(d)
+
+ file_info := fstat(d, allocator) or_return
+ defer file_info_delete(file_info, allocator)
+
+ if file_info.type != .Directory {
+ return
+ }
+
+ fis, _ := read_dir(d, -1, allocator)
+ slice.sort_by(fis, proc(a, b: File_Info) -> bool {
+ return a.name < b.name
+ })
+ defer file_info_slice_delete(fis, allocator)
+
+ for fi in fis {
+ matched := match(pattern, fi.name) or_return
+ if matched {
+ matched_path := join_path({dir, fi.name}, allocator) or_return
+ append(&m, matched_path)
+ }
+ }
+ return
+}
+
+@(private)
+has_meta :: proc(path: string) -> bool {
+ when ODIN_OS == .Windows {
+ CHARS :: `*?[`
+ } else {
+ CHARS :: `*?[\`
+ }
+ return strings.contains_any(path, CHARS)
+}
+
+@(private)
+clean_glob_path :: proc(path: string, temp_buf: []byte) -> (int, string) {
+ when ODIN_OS == .Windows {
+ vol_len := _volume_name_len(path)
+ switch {
+ case path == "":
+ return 0, "."
+ case vol_len+1 == len(path) && is_path_separator(path[len(path)-1]): // /, \, C:\, C:/
+ return vol_len+1, path
+ case vol_len == len(path) && len(path) == 2: // C:
+ copy(temp_buf[:], path)
+ temp_buf[2] = '.'
+ return vol_len, string(temp_buf[:3])
+ }
+
+ if vol_len >= len(path) {
+ vol_len = len(path) -1
+ }
+ return vol_len, path[:len(path)-1]
+ } else {
+ switch path {
+ case "":
+ return 0, "."
+ case Path_Separator_String:
+ return 0, path
+ }
+ return 0, path[:len(path)-1]
+ }
+}
\ No newline at end of file
diff --git a/core/os/os2/path_darwin.odin b/core/os/path_darwin.odin
similarity index 97%
rename from core/os/os2/path_darwin.odin
rename to core/os/path_darwin.odin
index 65aaf1e95..dbfdb584b 100644
--- a/core/os/os2/path_darwin.odin
+++ b/core/os/path_darwin.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_freebsd.odin b/core/os/path_freebsd.odin
similarity index 98%
rename from core/os/os2/path_freebsd.odin
rename to core/os/path_freebsd.odin
index e7e4f63c9..5ec43466e 100644
--- a/core/os/os2/path_freebsd.odin
+++ b/core/os/path_freebsd.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/path_js.odin b/core/os/path_js.odin
new file mode 100644
index 000000000..6ac845560
--- /dev/null
+++ b/core/os/path_js.odin
@@ -0,0 +1,85 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+_Path_Separator :: '/'
+_Path_Separator_String :: "/"
+_Path_List_Separator :: ':'
+
+_is_path_separator :: proc(c: byte) -> (ok: bool) {
+ return c == _Path_Separator
+}
+
+_mkdir :: proc(name: string, perm: int) -> (err: Error) {
+ return .Unsupported
+}
+
+_mkdir_all :: proc(path: string, perm: int) -> (err: Error) {
+ return .Unsupported
+}
+
+_remove_all :: proc(path: string) -> (err: Error) {
+ return .Unsupported
+}
+
+_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
+ return "", .Unsupported
+}
+
+_set_working_directory :: proc(dir: string) -> (err: Error) {
+ return .Unsupported
+}
+
+_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
+ return "", .Unsupported
+}
+
+_are_paths_identical :: proc(a, b: string) -> bool {
+ return false
+}
+
+_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) {
+ return
+}
+
+_is_absolute_path :: proc(path: string) -> bool {
+ return false
+}
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+ return "", .Unsupported
+}
+
+_get_relative_path_handle_start :: proc(base, target: string) -> bool {
+ return false
+}
+
+_get_common_path_len :: proc(base, target: string) -> int {
+ i := 0
+ end := min(len(base), len(target))
+ for j in 0..=end {
+ if j == end || _is_path_separator(base[j]) {
+ if base[i:j] == target[i:j] {
+ i = j
+ } else {
+ break
+ }
+ }
+ }
+ return i
+}
+
+_split_path :: proc(path: string) -> (dir, file: string) {
+ i := len(path) - 1
+ for i >= 0 && !_is_path_separator(path[i]) {
+ i -= 1
+ }
+ if i == 0 {
+ return path[:i+1], path[i+1:]
+ } else if i > 0 {
+ return path[:i], path[i+1:]
+ }
+ return "", path
+}
\ No newline at end of file
diff --git a/core/os/os2/path_linux.odin b/core/os/path_linux.odin
similarity index 99%
rename from core/os/os2/path_linux.odin
rename to core/os/path_linux.odin
index 1c9927843..ca68fffb1 100644
--- a/core/os/os2/path_linux.odin
+++ b/core/os/path_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_netbsd.odin b/core/os/path_netbsd.odin
similarity index 97%
rename from core/os/os2/path_netbsd.odin
rename to core/os/path_netbsd.odin
index 815102dea..a9ceb13df 100644
--- a/core/os/os2/path_netbsd.odin
+++ b/core/os/path_netbsd.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_openbsd.odin b/core/os/path_openbsd.odin
similarity index 99%
rename from core/os/os2/path_openbsd.odin
rename to core/os/path_openbsd.odin
index cbc0346d4..55b6b7d1f 100644
--- a/core/os/os2/path_openbsd.odin
+++ b/core/os/path_openbsd.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_posix.odin b/core/os/path_posix.odin
similarity index 99%
rename from core/os/os2/path_posix.odin
rename to core/os/path_posix.odin
index 173cb6b6d..a877af3e6 100644
--- a/core/os/os2/path_posix.odin
+++ b/core/os/path_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_posixfs.odin b/core/os/path_posixfs.odin
similarity index 99%
rename from core/os/os2/path_posixfs.odin
rename to core/os/path_posixfs.odin
index 0736e73d1..aea89c60a 100644
--- a/core/os/os2/path_posixfs.odin
+++ b/core/os/path_posixfs.odin
@@ -1,6 +1,6 @@
#+private
#+build linux, darwin, netbsd, freebsd, openbsd, wasi
-package os2
+package os
// This implementation is for all systems that have POSIX-compliant filesystem paths.
diff --git a/core/os/os2/path_wasi.odin b/core/os/path_wasi.odin
similarity index 99%
rename from core/os/os2/path_wasi.odin
rename to core/os/path_wasi.odin
index f26e16158..aa7740497 100644
--- a/core/os/os2/path_wasi.odin
+++ b/core/os/path_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/path_windows.odin b/core/os/path_windows.odin
similarity index 99%
rename from core/os/os2/path_windows.odin
rename to core/os/path_windows.odin
index ce3828755..6ccab1dab 100644
--- a/core/os/os2/path_windows.odin
+++ b/core/os/path_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
import "core:strings"
@@ -157,6 +157,7 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
}
}
+@(private)
can_use_long_paths: bool
@(init)
@@ -355,4 +356,4 @@ _split_path :: proc(path: string) -> (dir, file: string) {
return path[:i], path[i+1:]
}
return "", path
-}
+}
\ No newline at end of file
diff --git a/core/os/os2/pipe.odin b/core/os/pipe.odin
similarity index 94%
rename from core/os/os2/pipe.odin
rename to core/os/pipe.odin
index 5d3e8368e..5254f9469 100644
--- a/core/os/os2/pipe.odin
+++ b/core/os/pipe.odin
@@ -1,4 +1,4 @@
-package os2
+package os
/*
Create an anonymous pipe.
@@ -15,7 +15,7 @@ process, that end of the pipe needs to be closed by the parent, before any data
is attempted to be read.
Although pipes look like files and is compatible with most file APIs in package
-os2, the way it's meant to be read is different. Due to asynchronous nature of
+os, the way it's meant to be read is different. Due to asynchronous nature of
the communication channel, the data may not be present at the time of a read
request. The other scenario is when a pipe has no data because the other end
of the pipe was closed by the child process.
diff --git a/core/os/pipe_js.odin b/core/os/pipe_js.odin
new file mode 100644
index 000000000..aea5e9a83
--- /dev/null
+++ b/core/os/pipe_js.odin
@@ -0,0 +1,14 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+_pipe :: proc() -> (r, w: ^File, err: Error) {
+ err = .Unsupported
+ return
+}
+
+@(require_results)
+_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
+ err = .Unsupported
+ return
+}
diff --git a/core/os/os2/pipe_linux.odin b/core/os/pipe_linux.odin
similarity index 98%
rename from core/os/os2/pipe_linux.odin
rename to core/os/pipe_linux.odin
index bb4456e1c..561f82f80 100644
--- a/core/os/os2/pipe_linux.odin
+++ b/core/os/pipe_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:sys/linux"
diff --git a/core/os/os2/pipe_posix.odin b/core/os/pipe_posix.odin
similarity index 99%
rename from core/os/os2/pipe_posix.odin
rename to core/os/pipe_posix.odin
index 7c07bc068..e811e306f 100644
--- a/core/os/os2/pipe_posix.odin
+++ b/core/os/pipe_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "core:sys/posix"
import "core:strings"
diff --git a/core/os/os2/pipe_wasi.odin b/core/os/pipe_wasi.odin
similarity index 94%
rename from core/os/os2/pipe_wasi.odin
rename to core/os/pipe_wasi.odin
index 19c11b51d..e27c9419e 100644
--- a/core/os/os2/pipe_wasi.odin
+++ b/core/os/pipe_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
_pipe :: proc() -> (r, w: ^File, err: Error) {
err = .Unsupported
diff --git a/core/os/os2/pipe_windows.odin b/core/os/pipe_windows.odin
similarity index 98%
rename from core/os/os2/pipe_windows.odin
rename to core/os/pipe_windows.odin
index d6dc47c9c..4e627e26a 100644
--- a/core/os/os2/pipe_windows.odin
+++ b/core/os/pipe_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import win32 "core:sys/windows"
diff --git a/core/os/os2/process.odin b/core/os/process.odin
similarity index 98%
rename from core/os/os2/process.odin
rename to core/os/process.odin
index 201d4f6e7..9cdaf0457 100644
--- a/core/os/os2/process.odin
+++ b/core/os/process.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
@@ -120,6 +120,21 @@ get_ppid :: proc() -> int {
return _get_ppid()
}
+/*
+Obtain the current thread id
+*/
+@(require_results)
+get_current_thread_id :: proc "contextless" () -> int {
+ return _get_current_thread_id()
+}
+
+/*
+Return the number of cores
+*/
+get_processor_core_count :: proc() -> int {
+ return _get_processor_core_count()
+}
+
/*
Obtain ID's of all processes running in the system.
*/
diff --git a/core/os/process_freebsd.odin b/core/os/process_freebsd.odin
new file mode 100644
index 000000000..ccc2549db
--- /dev/null
+++ b/core/os/process_freebsd.odin
@@ -0,0 +1,36 @@
+#+private
+#+build freebsd
+package os
+
+import "core:c"
+
+foreign import libc "system:c"
+foreign import dl "system:dl"
+
+foreign libc {
+ @(link_name="sysctlbyname")
+ _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+}
+
+foreign dl {
+ @(link_name="pthread_getthreadid_np")
+ pthread_getthreadid_np :: proc() -> c.int ---
+}
+
+@(require_results)
+_get_current_thread_id :: proc "contextless" () -> int {
+ return int(pthread_getthreadid_np())
+}
+
+@(require_results)
+_get_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
+}
\ No newline at end of file
diff --git a/core/os/process_js.odin b/core/os/process_js.odin
new file mode 100644
index 000000000..6283d270c
--- /dev/null
+++ b/core/os/process_js.odin
@@ -0,0 +1,95 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+import "core:time"
+
+
+_exit :: proc "contextless" (code: int) -> ! {
+ runtime.panic_contextless("exit")
+}
+
+_get_uid :: proc() -> int {
+ return 0
+}
+
+_get_euid :: proc() -> int {
+ return 0
+}
+
+_get_gid :: proc() -> int {
+ return 0
+}
+
+_get_egid :: proc() -> int {
+ return 0
+}
+
+_get_pid :: proc() -> int {
+ return 0
+}
+
+_get_ppid :: proc() -> int {
+ return 0
+}
+
+_get_current_thread_id :: proc "contextless" () -> int {
+ return 0
+}
+
+_get_processor_core_count :: proc() -> int {
+ return 1
+}
+
+_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_process_close :: proc(process: Process) -> Error {
+ return .Unsupported
+}
+
+_process_kill :: proc(process: Process) -> (err: Error) {
+ return .Unsupported
+}
+
+_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
+ err = .Unsupported
+ return
+}
+
+_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) {
+ process.pid = pid
+ err = .Unsupported
+ return
+}
+
+_process_handle_still_valid :: proc(p: Process) -> Error {
+ return nil
+}
+
+_process_state_update_times :: proc(p: Process, state: ^Process_State) {
+ return
+}
diff --git a/core/os/os2/process_linux.odin b/core/os/process_linux.odin
similarity index 97%
rename from core/os/os2/process_linux.odin
rename to core/os/process_linux.odin
index 197693dc3..7041e16b7 100644
--- a/core/os/os2/process_linux.odin
+++ b/core/os/process_linux.odin
@@ -1,16 +1,24 @@
#+build linux
#+private file
-package os2
+package os
import "base:runtime"
import "base:intrinsics"
+import "core:c"
import "core:time"
import "core:slice"
import "core:strings"
import "core:strconv"
+import "core:sys/unix"
import "core:sys/linux"
+foreign import libc "system:c"
+
+foreign libc {
+ @(link_name="get_nprocs") _unix_get_nprocs :: proc() -> c.int ---
+}
+
PIDFD_UNASSIGNED :: ~uintptr(0)
@(private="package")
@@ -43,6 +51,22 @@ _get_ppid :: proc() -> int {
return int(linux.getppid())
}
+@(private="package")
+_get_current_thread_id :: proc "contextless" () -> int {
+ return unix.sys_gettid()
+}
+
+@(private="package")
+_get_processor_core_count :: proc() -> (core_count: int) {
+ cpu_set: [128]u64
+ if n, err := linux.sched_getaffinity(0, size_of(cpu_set), &cpu_set); err == nil {
+ for set in cpu_set[:n / 8] {
+ core_count += int(intrinsics.count_ones(set))
+ }
+ }
+ return
+}
+
@(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
diff --git a/core/os/process_netbsd.odin b/core/os/process_netbsd.odin
new file mode 100644
index 000000000..45ca03178
--- /dev/null
+++ b/core/os/process_netbsd.odin
@@ -0,0 +1,31 @@
+#+private
+#+build netbsd
+package os
+
+import "core:c"
+foreign import libc "system:c"
+
+@(private)
+foreign libc {
+ _lwp_self :: proc() -> i32 ---
+
+ @(link_name="sysctlbyname")
+ _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+}
+
+@(require_results)
+_get_current_thread_id :: proc "contextless" () -> int {
+ return int(_lwp_self())
+}
+
+_get_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
+}
\ No newline at end of file
diff --git a/core/os/process_openbsd.odin b/core/os/process_openbsd.odin
new file mode 100644
index 000000000..5195261ff
--- /dev/null
+++ b/core/os/process_openbsd.odin
@@ -0,0 +1,25 @@
+#+private
+#+build openbsd
+package os
+
+import "core:c"
+
+foreign import libc "system:c"
+
+@(default_calling_convention="c")
+foreign libc {
+ @(link_name="getthrid") _unix_getthrid :: proc() -> int ---
+ @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long ---
+}
+
+@(require_results)
+_get_current_thread_id :: proc "contextless" () -> int {
+ return _unix_getthrid()
+}
+
+_SC_NPROCESSORS_ONLN :: 503
+
+@(private, require_results)
+_get_processor_core_count :: proc() -> int {
+ return int(_sysconf(_SC_NPROCESSORS_ONLN))
+}
\ No newline at end of file
diff --git a/core/os/os2/process_posix.odin b/core/os/process_posix.odin
similarity index 99%
rename from core/os/os2/process_posix.odin
rename to core/os/process_posix.odin
index a48e44900..d3ec543f4 100644
--- a/core/os/os2/process_posix.odin
+++ b/core/os/process_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/process_posix_darwin.odin
similarity index 90%
rename from core/os/os2/process_posix_darwin.odin
rename to core/os/process_posix_darwin.odin
index f655d42a9..a75b4ef96 100644
--- a/core/os/os2/process_posix_darwin.odin
+++ b/core/os/process_posix_darwin.odin
@@ -1,23 +1,50 @@
#+private
-package os2
+package os
import "base:runtime"
import "base:intrinsics"
import "core:bytes"
+import "core:c"
import "core:sys/darwin"
import "core:sys/posix"
import "core:sys/unix"
import "core:time"
-foreign import lib "system:System"
+foreign import libc "system:System"
+foreign import pthread "system:System"
-foreign lib {
+foreign libc {
sysctl :: proc "c" (
name: [^]i32, namelen: u32,
oldp: rawptr, oldlenp: ^uint,
newp: rawptr, newlen: uint,
) -> posix.result ---
+
+ @(link_name="sysctlbyname")
+ _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
+}
+
+_get_current_thread_id :: proc "contextless" () -> int {
+ tid: u64
+ // NOTE(Oskar): available from OSX 10.6 and iOS 3.2.
+ // For older versions there is `syscall(SYS_thread_selfid)`, but not really
+ // the same thing apparently.
+ foreign pthread { pthread_threadid_np :: proc "c" (rawptr, ^u64) -> c.int --- }
+ pthread_threadid_np(nil, &tid)
+ return int(tid)
+}
+
+_get_processor_core_count :: proc() -> int {
+ count : int = 0
+ count_size := size_of(count)
+ if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
+ if count > 0 {
+ return count
+ }
+ }
+
+ return 1
}
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
diff --git a/core/os/os2/process_posix_other.odin b/core/os/process_posix_other.odin
similarity index 98%
rename from core/os/os2/process_posix_other.odin
rename to core/os/process_posix_other.odin
index 65da3e9e2..85ed6cdd8 100644
--- a/core/os/os2/process_posix_other.odin
+++ b/core/os/process_posix_other.odin
@@ -1,6 +1,6 @@
#+private
#+build netbsd, openbsd, freebsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/process_wasi.odin b/core/os/process_wasi.odin
similarity index 92%
rename from core/os/os2/process_wasi.odin
rename to core/os/process_wasi.odin
index 52fdb1680..e18fc0524 100644
--- a/core/os/os2/process_wasi.odin
+++ b/core/os/process_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
@@ -30,6 +30,14 @@ _get_ppid :: proc() -> int {
return 0
}
+_get_current_thread_id :: proc "contextless" () -> int {
+ return 0
+}
+
+_get_processor_core_count :: proc() -> int {
+ return 1
+}
+
_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
err = .Unsupported
return
diff --git a/core/os/os2/process_windows.odin b/core/os/process_windows.odin
similarity index 96%
rename from core/os/os2/process_windows.odin
rename to core/os/process_windows.odin
index 05ac9da93..e6db5b4e9 100644
--- a/core/os/os2/process_windows.odin
+++ b/core/os/process_windows.odin
@@ -1,6 +1,7 @@
#+private file
-package os2
+package os
+import "base:intrinsics"
import "base:runtime"
import "core:strings"
@@ -50,6 +51,35 @@ _get_ppid :: proc() -> int {
return -1
}
+@(private="package")
+_get_current_thread_id :: proc "contextless" () -> int {
+ return int(win32.GetCurrentThreadId())
+}
+
+@(private="package")
+_get_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
+}
+
@(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0)
diff --git a/core/os/stat.odin b/core/os/stat.odin
index 21a4961d1..fa92e8a8e 100644
--- a/core/os/stat.odin
+++ b/core/os/stat.odin
@@ -1,33 +1,117 @@
package os
+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, // allocated
- name: string, // uses `fullpath` as underlying data
- size: i64,
- mode: File_Mode,
- is_dir: bool,
+ 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,
}
-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)
+@(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 := context.allocator) {
+file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) {
delete(fi.fullpath, allocator)
}
-File_Mode :: distinct u32
+@(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
+}
-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)
+/*
+ `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)
+}
\ No newline at end of file
diff --git a/core/os/stat_js.odin b/core/os/stat_js.odin
new file mode 100644
index 000000000..bad75486b
--- /dev/null
+++ b/core/os/stat_js.odin
@@ -0,0 +1,25 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+ return {}, .Unsupported
+}
+
+_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+ return {}, .Unsupported
+}
+
+_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
+ return {}, .Unsupported
+}
+
+_same_file :: proc(fi1, fi2: File_Info) -> bool {
+ return fi1.fullpath == fi2.fullpath
+}
+
+_is_reserved_name :: proc(path: string) -> bool {
+ return false
+}
\ No newline at end of file
diff --git a/core/os/os2/stat_linux.odin b/core/os/stat_linux.odin
similarity index 97%
rename from core/os/os2/stat_linux.odin
rename to core/os/stat_linux.odin
index 6185252cf..082279e8d 100644
--- a/core/os/os2/stat_linux.odin
+++ b/core/os/stat_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "core:time"
import "base:runtime"
@@ -73,3 +73,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
+
+_is_reserved_name :: proc(path: string) -> bool {
+ return false
+}
\ No newline at end of file
diff --git a/core/os/os2/stat_posix.odin b/core/os/stat_posix.odin
similarity index 97%
rename from core/os/os2/stat_posix.odin
rename to core/os/stat_posix.odin
index 4ed96b389..924860744 100644
--- a/core/os/os2/stat_posix.odin
+++ b/core/os/stat_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
@@ -135,3 +135,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
+
+_is_reserved_name :: proc(path: string) -> bool {
+ return false
+}
\ No newline at end of file
diff --git a/core/os/os2/stat_wasi.odin b/core/os/stat_wasi.odin
similarity index 96%
rename from core/os/os2/stat_wasi.odin
rename to core/os/stat_wasi.odin
index bf18d8273..5f5e6fe45 100644
--- a/core/os/os2/stat_wasi.odin
+++ b/core/os/stat_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
@@ -98,3 +98,7 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
_same_file :: proc(fi1, fi2: File_Info) -> bool {
return fi1.fullpath == fi2.fullpath
}
+
+_is_reserved_name :: proc(path: string) -> bool {
+ return false
+}
\ No newline at end of file
diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin
index 662c9f9e6..51bd57d5b 100644
--- a/core/os/stat_windows.odin
+++ b/core/os/stat_windows.odin
@@ -1,51 +1,82 @@
+#+private
package os
-import "core:time"
import "base:runtime"
+import "core:time"
+import "core:strings"
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
-
+_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 = "."
}
- 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
+ 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)
}
-@(private, require_results)
-_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) {
+internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
if len(name) == 0 {
- return {}, ERROR_PATH_NOT_FOUND
+ return {}, .Not_Exist
}
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- 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)
+ 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
- return file_info_from_win32_file_attribute_data(&fa, name)
+ 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()
@@ -54,65 +85,28 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al
fd: win32.WIN32_FIND_DATAW
sh := win32.FindFirstFileW(wname, &fd)
if sh == win32.INVALID_HANDLE_VALUE {
- e = get_last_error()
+ e = _get_platform_error()
return
}
win32.FindClose(sh)
- return file_info_from_win32_find_data(&fd, name)
+ 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_last_error()
+ e = _get_platform_error()
return
}
defer win32.CloseHandle(h)
- return file_info_from_get_file_information_by_handle(name, h)
+ return _file_info_from_get_file_information_by_handle(name, h, allocator)
}
-
-@(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 {
+_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
buf := buf
N := 0
for c, i in buf {
@@ -121,50 +115,59 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 {
}
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] = '\\'
+ if len(buf) >= 4 {
+ if buf[0] == '\\' &&
+ buf[1] == '\\' &&
+ buf[2] == '?' &&
+ buf[3] == '\\' {
+ buf = buf[4:]
}
}
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
+_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) {
+ if f == nil {
+ return "", nil
}
- h := win32.HANDLE(fd)
+ h := _handle(f)
n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0)
if n == 0 {
- return nil, get_last_error()
+ return "", _get_platform_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 ""
+
+ 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)
}
-@(private, require_results)
basename :: proc(name: string) -> (base: string) {
name := name
if len(name) > 3 && name[:3] == `\\?` {
@@ -190,114 +193,201 @@ basename :: proc(name: string) -> (base: string) {
return name
}
-@(private, require_results)
-file_type_mode :: proc(h: win32.HANDLE) -> File_Mode {
+file_type :: proc(h: win32.HANDLE) -> File_Type {
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
+ case win32.FILE_TYPE_PIPE: return .Named_Pipe
+ case win32.FILE_TYPE_CHAR: return .Character_Device
+ case win32.FILE_TYPE_DISK: return .Regular
}
- return 0
+ 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)
+}
-@(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
+_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 |= 0o666
+ mode += Permissions_Read_Write_All
}
is_sym := false
- if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
is_sym = false
} else {
is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT
}
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)
- }
+ 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
}
-@(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))
+// 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)
}
-@(private, require_results)
-file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) {
+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)
-
- 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)
+ 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
}
-@(private, require_results)
-file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) {
+_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
-
- fi.mode |= file_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
- fi.is_dir = fi.mode & File_Mode_Dir != 0
-
- windows_set_file_info_times(&fi, d)
-
- fi.fullpath, e = full_path_from_name(name)
+ 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
}
-@(private, require_results)
-file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) {
+_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) {
- err := get_last_error()
- return {}, err
+ return {}, _get_platform_error()
}
ti: win32.FILE_ATTRIBUTE_TAG_INFO
if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) {
- err := get_last_error()
- if err != ERROR_INVALID_PARAMETER {
+ 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.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)
-
+ 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.. 0 {
+ slash_count += 1
+
+ if slash_count == 2 {
+ return i
+ }
+ }
+ }
+
+ return len(path)
+}
\ No newline at end of file
diff --git a/core/os/os2/temp_file.odin b/core/os/temp_file.odin
similarity index 99%
rename from core/os/os2/temp_file.odin
rename to core/os/temp_file.odin
index 2c0236428..be10eb22e 100644
--- a/core/os/os2/temp_file.odin
+++ b/core/os/temp_file.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/temp_file_js.odin b/core/os/temp_file_js.odin
new file mode 100644
index 000000000..32ce1c484
--- /dev/null
+++ b/core/os/temp_file_js.odin
@@ -0,0 +1,9 @@
+#+build js wasm32, js wasm64p32
+#+private
+package os
+
+import "base:runtime"
+
+_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
+ return "", .Mode_Not_Implemented
+}
\ No newline at end of file
diff --git a/core/os/os2/temp_file_linux.odin b/core/os/temp_file_linux.odin
similarity index 96%
rename from core/os/os2/temp_file_linux.odin
rename to core/os/temp_file_linux.odin
index 310720cbe..30f2169ad 100644
--- a/core/os/os2/temp_file_linux.odin
+++ b/core/os/temp_file_linux.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/temp_file_posix.odin b/core/os/temp_file_posix.odin
similarity index 97%
rename from core/os/os2/temp_file_posix.odin
rename to core/os/temp_file_posix.odin
index b44ea13a7..a72dc2fab 100644
--- a/core/os/os2/temp_file_posix.odin
+++ b/core/os/temp_file_posix.odin
@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/temp_file_wasi.odin
similarity index 95%
rename from core/os/os2/temp_file_wasi.odin
rename to core/os/temp_file_wasi.odin
index d5628d300..d3fa941f7 100644
--- a/core/os/os2/temp_file_wasi.odin
+++ b/core/os/temp_file_wasi.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/temp_file_windows.odin b/core/os/temp_file_windows.odin
similarity index 97%
rename from core/os/os2/temp_file_windows.odin
rename to core/os/temp_file_windows.odin
index 91ea284a1..4c927134d 100644
--- a/core/os/os2/temp_file_windows.odin
+++ b/core/os/temp_file_windows.odin
@@ -1,5 +1,5 @@
#+private
-package os2
+package os
import "base:runtime"
import win32 "core:sys/windows"
diff --git a/core/os/os2/user.odin b/core/os/user.odin
similarity index 99%
rename from core/os/os2/user.odin
rename to core/os/user.odin
index e2a4ec4d0..8e952477d 100644
--- a/core/os/os2/user.odin
+++ b/core/os/user.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
diff --git a/core/os/os2/user_posix.odin b/core/os/user_posix.odin
similarity index 99%
rename from core/os/os2/user_posix.odin
rename to core/os/user_posix.odin
index 09134d847..355a2e3cd 100644
--- a/core/os/os2/user_posix.odin
+++ b/core/os/user_posix.odin
@@ -1,6 +1,7 @@
#+build !windows
-package os2
+package os
+import "base:intrinsics"
import "base:runtime"
import "core:strings"
diff --git a/core/os/os2/user_windows.odin b/core/os/user_windows.odin
similarity index 99%
rename from core/os/os2/user_windows.odin
rename to core/os/user_windows.odin
index 75d0ba6ac..6f7b74d6d 100644
--- a/core/os/os2/user_windows.odin
+++ b/core/os/user_windows.odin
@@ -1,4 +1,4 @@
-package os2
+package os
import "base:runtime"
@(require) import win32 "core:sys/windows"
diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin
index 3eaa7c6fe..178d5f986 100644
--- a/core/path/filepath/match.odin
+++ b/core/path/filepath/match.odin
@@ -3,14 +3,6 @@
package filepath
import "core:os"
-import "core:slice"
-import "core:strings"
-import "core:unicode/utf8"
-
-Match_Error :: enum {
- None,
- Syntax_Error,
-}
// match states whether "name" matches the shell pattern
// Pattern syntax is:
@@ -34,183 +26,7 @@ Match_Error :: enum {
//
// NOTE(bill): This is effectively the shell pattern matching system found
//
-match :: proc(pattern, name: string) -> (matched: bool, err: Match_Error) {
- pattern, name := pattern, name
- pattern_loop: for len(pattern) > 0 {
- star: bool
- chunk: string
- star, chunk, pattern = scan_chunk(pattern)
- if star && chunk == "" {
- return !strings.contains(name, SEPARATOR_STRING), .None
- }
-
- t: string
- ok: bool
- t, ok, err = match_chunk(chunk, name)
-
- if ok && (len(t) == 0 || len(pattern) > 0) {
- name = t
- continue
- }
- if err != .None {
- return
- }
- if star {
- for i := 0; i < len(name) && name[i] != SEPARATOR; i += 1 {
- t, ok, err = match_chunk(chunk, name[i+1:])
- if ok {
- if len(pattern) == 0 && len(t) > 0 {
- continue
- }
- name = t
- continue pattern_loop
- }
- if err != .None {
- return
- }
- }
- }
-
- return false, .None
- }
-
- return len(name) == 0, .None
-}
-
-
-@(private="file")
-scan_chunk :: proc(pattern: string) -> (star: bool, chunk, rest: string) {
- pattern := pattern
- for len(pattern) > 0 && pattern[0] == '*' {
- pattern = pattern[1:]
- star = true
- }
-
- in_range, i := false, 0
-
- scan_loop: for i = 0; i < len(pattern); i += 1 {
- switch pattern[i] {
- case '\\':
- when ODIN_OS != .Windows {
- if i+1 < len(pattern) {
- i += 1
- }
- }
- case '[':
- in_range = true
- case ']':
- in_range = false
- case '*':
- in_range or_break scan_loop
-
- }
- }
- return star, pattern[:i], pattern[i:]
-}
-
-@(private="file")
-match_chunk :: proc(chunk, s: string) -> (rest: string, ok: bool, err: Match_Error) {
- chunk, s := chunk, s
- for len(chunk) > 0 {
- if len(s) == 0 {
- return
- }
- switch chunk[0] {
- case '[':
- r, w := utf8.decode_rune_in_string(s)
- s = s[w:]
- chunk = chunk[1:]
- is_negated := false
- if len(chunk) > 0 && chunk[0] == '^' {
- is_negated = true
- chunk = chunk[1:]
- }
- match := false
- range_count := 0
- for {
- if len(chunk) > 0 && chunk[0] == ']' && range_count > 0 {
- chunk = chunk[1:]
- break
- }
- lo, hi: rune
- if lo, chunk, err = get_escape(chunk); err != .None {
- return
- }
- hi = lo
- if chunk[0] == '-' {
- if hi, chunk, err = get_escape(chunk[1:]); err != .None {
- return
- }
- }
-
- if lo <= r && r <= hi {
- match = true
- }
- range_count += 1
- }
- if match == is_negated {
- return
- }
-
- case '?':
- if s[0] == SEPARATOR {
- return
- }
- _, w := utf8.decode_rune_in_string(s)
- s = s[w:]
- chunk = chunk[1:]
-
- case '\\':
- when ODIN_OS != .Windows {
- chunk = chunk[1:]
- if len(chunk) == 0 {
- err = .Syntax_Error
- return
- }
- }
- fallthrough
- case:
- if chunk[0] != s[0] {
- return
- }
- s = s[1:]
- chunk = chunk[1:]
-
- }
- }
- return s, true, .None
-}
-
-@(private="file")
-get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Error) {
- if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
- err = .Syntax_Error
- return
- }
- chunk := chunk
- if chunk[0] == '\\' && ODIN_OS != .Windows {
- chunk = chunk[1:]
- if len(chunk) == 0 {
- err = .Syntax_Error
- return
- }
- }
-
- w: int
- r, w = utf8.decode_rune_in_string(chunk)
- if r == utf8.RUNE_ERROR && w == 1 {
- err = .Syntax_Error
- }
-
- next_chunk = chunk[w:]
- if len(next_chunk) == 0 {
- err = .Syntax_Error
- }
-
- return
-}
-
-
+match :: os.match
// glob returns the names of all files matching pattern or nil if there are no matching files
// The syntax of patterns is the same as "match".
@@ -218,145 +34,4 @@ get_escape :: proc(chunk: string) -> (r: rune, next_chunk: string, err: Match_Er
//
// glob ignores file system errors
//
-glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []string, err: Match_Error) {
- context.allocator = allocator
-
- if !has_meta(pattern) {
- // TODO(bill): os.lstat on here to check for error
- m := make([]string, 1)
- m[0] = pattern
- return m[:], .None
- }
-
- dir, file := split(pattern)
- volume_len := 0
- when ODIN_OS == .Windows {
- temp_buf: [8]byte
- volume_len, dir = clean_glob_path_windows(dir, temp_buf[:])
-
- } else {
- dir = clean_glob_path(dir)
- }
-
- if !has_meta(dir[volume_len:]) {
- m, e := _glob(dir, file, nil)
- return m[:], e
- }
-
- m: []string
- m, err = glob(dir)
- if err != .None {
- return
- }
- defer {
- for s in m {
- delete(s)
- }
- delete(m)
- }
-
- dmatches := make([dynamic]string, 0, 0)
- for d in m {
- dmatches, err = _glob(d, file, &dmatches)
- if err != .None {
- break
- }
- }
- if len(dmatches) > 0 {
- matches = dmatches[:]
- }
- return
-}
-
-// Internal implementation of `glob`, not meant to be used by the user. Prefer `glob`.
-_glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := context.allocator) -> (m: [dynamic]string, e: Match_Error) {
- context.allocator = allocator
-
- if matches != nil {
- m = matches^
- } else {
- m = make([dynamic]string, 0, 0)
- }
-
-
- d, derr := os.open(dir, os.O_RDONLY)
- if derr != nil {
- return
- }
- defer os.close(d)
-
- {
- file_info, ferr := os.fstat(d)
- defer os.file_info_delete(file_info)
-
- if ferr != nil {
- return
- }
- if !file_info.is_dir {
- return
- }
- }
-
-
- fis, _ := os.read_dir(d, -1)
- slice.sort_by(fis, proc(a, b: os.File_Info) -> bool {
- return a.name < b.name
- })
- defer {
- for fi in fis {
- os.file_info_delete(fi)
- }
- delete(fis)
- }
-
- for fi in fis {
- n := fi.name
- matched := match(pattern, n) or_return
- if matched {
- append(&m, join({dir, n}))
- }
- }
- return
-}
-
-@(private)
-has_meta :: proc(path: string) -> bool {
- when ODIN_OS == .Windows {
- CHARS :: `*?[`
- } else {
- CHARS :: `*?[\`
- }
- return strings.contains_any(path, CHARS)
-}
-
-@(private)
-clean_glob_path :: proc(path: string) -> string {
- switch path {
- case "":
- return "."
- case SEPARATOR_STRING:
- return path
- }
- return path[:len(path)-1]
-}
-
-
-@(private)
-clean_glob_path_windows :: proc(path: string, temp_buf: []byte) -> (prefix_len: int, cleaned: string) {
- vol_len := volume_name_len(path)
- switch {
- case path == "":
- return 0, "."
- case vol_len+1 == len(path) && is_separator(path[len(path)-1]): // /, \, C:\, C:/
- return vol_len+1, path
- case vol_len == len(path) && len(path) == 2: // C:
- copy(temp_buf[:], path)
- temp_buf[2] = '.'
- return vol_len, string(temp_buf[:3])
- }
-
- if vol_len >= len(path) {
- vol_len = len(path) -1
- }
- return vol_len, path[:len(path)-1]
-}
+glob :: os.glob
\ No newline at end of file
diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin
index dbad98fa1..58dc06103 100644
--- a/core/path/filepath/path.odin
+++ b/core/path/filepath/path.odin
@@ -2,35 +2,37 @@
// To process paths such as URLs that depend on forward slashes regardless of the OS, use the slashpath package.
package filepath
-import "base:runtime"
+import "core:os"
import "core:strings"
SEPARATOR_CHARS :: `/\`
// is_separator checks whether the byte is a valid separator character
-is_separator :: proc(c: byte) -> bool {
- switch c {
- case '/': return true
- case '\\': return ODIN_OS == .Windows
- }
- return false
-}
+is_separator :: os.is_path_separator
-@(private)
-is_slash :: proc(c: byte) -> bool {
- return c == '\\' || c == '/'
-}
+/*
+ In Windows, returns `true` if `path` is one of the following:
+ "CON", "PRN", "AUX", "NUL",
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+
+ On other platforms, returns `false`.
+*/
+is_reserved_name :: os.is_reserved_name
// Splits path immediate following the last separator; separating the path into a directory and file.
// If no separator is found, `dir` will be empty and `path` set to `path`.
-split :: proc(path: string) -> (dir, file: string) {
- vol := volume_name(path)
- i := len(path) - 1
- for i >= len(vol) && !is_separator(path[i]) {
- i -= 1
- }
- return path[:i+1], path[i+1:]
-}
+split :: os.split_path
+
+
+/*
+Join all `elems` with the system's path separator and normalize the result.
+
+*Allocates Using Provided Allocator*
+
+For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`.
+*/
+join :: os.join_path
/*
Returns leading volume name.
@@ -39,79 +41,7 @@ split :: proc(path: string) -> (dir, file: string) {
"C:\foo\bar\baz" will return "C:" on Windows.
Everything else will be "".
*/
-volume_name :: proc(path: string) -> string {
- return path[:volume_name_len(path)]
-}
-
-// Returns the length of the volume name in bytes.
-volume_name_len :: proc(path: string) -> int {
- if ODIN_OS == .Windows {
- 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_separator(path[0]) || !is_separator(path[1]) {
- return 0
- }
-
- // Device path. The volume name is the whole string
- if len(path) >= 5 && path[2] == '.' && is_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_separator(path[3]) {
- if is_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.. 0 {
- slash_count += 1
-
- if slash_count == 2 {
- return i
- }
- }
- }
-
- return len(path)
- }
- return 0
-}
+volume_name :: os.volume_name
/*
Gets the file name and extension from a path.
@@ -123,30 +53,7 @@ volume_name_len :: proc(path: string) -> int {
Returns "." if the path is an empty string.
*/
-base :: proc(path: string) -> string {
- if path == "" {
- return "."
- }
-
- path := path
- for len(path) > 0 && is_separator(path[len(path)-1]) {
- path = path[:len(path)-1]
- }
-
- path = path[volume_name_len(path):]
-
- i := len(path)-1
- for i >= 0 && !is_separator(path[i]) {
- i -= 1
- }
- if i >= 0 {
- path = path[i+1:]
- }
- if path == "" {
- return SEPARATOR_STRING
- }
- return path
-}
+base :: os.base
/*
Gets the name of a file from a path.
@@ -163,24 +70,7 @@ base :: proc(path: string) -> string {
Returns an empty string if there is no stem. e.g: '.gitignore'.
Returns an empty string if there's a trailing path separator.
*/
-stem :: proc(path: string) -> string {
- if len(path) > 0 && is_separator(path[len(path) - 1]) {
- // NOTE(tetra): Trailing separator
- return ""
- }
-
- // NOTE(tetra): Get the basename
- path := path
- if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 {
- path = path[i+1:]
- }
-
- if i := strings.last_index_byte(path, '.'); i != -1 {
- return path[:i]
- }
-
- return path
-}
+stem :: os.stem
/*
Gets the name of a file from a path.
@@ -196,13 +86,7 @@ stem :: proc(path: string) -> string {
Returns an empty string if there is no stem. e.g: '.gitignore'.
Returns an empty string if there's a trailing path separator.
*/
-short_stem :: proc(path: string) -> string {
- s := stem(path)
- if i := strings.index_byte(s, '.'); i != -1 {
- return s[:i]
- }
- return s
-}
+short_stem :: os.short_stem
/*
Gets the file extension from a path, including the dot.
@@ -219,14 +103,7 @@ short_stem :: proc(path: string) -> string {
Returns an empty string if there is no dot.
Returns an empty string if there is a trailing path separator.
*/
-ext :: proc(path: string) -> string {
- for i := len(path)-1; i >= 0 && !is_separator(path[i]); i -= 1 {
- if path[i] == '.' {
- return path[i:]
- }
- }
- return ""
-}
+ext :: os.ext
/*
Gets the file extension from a path, including the dot.
@@ -242,24 +119,7 @@ ext :: proc(path: string) -> string {
Returns an empty string if there is no dot.
Returns an empty string if there is a trailing path separator.
*/
-long_ext :: proc(path: string) -> string {
- if len(path) > 0 && is_separator(path[len(path) - 1]) {
- // NOTE(tetra): Trailing separator
- return ""
- }
-
- // NOTE(tetra): Get the basename
- path := path
- if i := strings.last_index_any(path, SEPARATOR_CHARS); i != -1 {
- path = path[i+1:]
- }
-
- if i := strings.index_byte(path, '.'); i != -1 {
- return path[i:]
- }
-
- return ""
-}
+long_ext :: os.long_ext
/*
Returns the shortest path name equivalent to `path` through solely lexical processing.
@@ -276,108 +136,30 @@ long_ext :: proc(path: string) -> string {
If the result of the path is an empty string, the returned path with be `"."`.
*/
-clean :: proc(path: string, allocator := context.allocator) -> (cleaned: string, err: runtime.Allocator_Error) #optional_allocator_error {
- context.allocator = allocator
+clean :: os.clean_path
- path := path
- original_path := path
- vol_len := volume_name_len(path)
- path = path[vol_len:]
+/*
+Returns the result of replacing each path separator character in the path
+with the specific character `new_sep`.
- if path == "" {
- if vol_len > 1 && original_path[1] != ':' {
- s, ok := from_slash(original_path)
- if !ok {
- s = strings.clone(s) or_return
- }
- return s, nil
- }
- return strings.concatenate({original_path, "."})
- }
+*Allocates Using Provided Allocator*
+*/
+replace_path_separators := os.replace_path_separators
- rooted := is_separator(path[0])
+/*
+Return true if `path` is an absolute path as opposed to a relative one.
+*/
+is_abs :: os.is_absolute_path
- n := len(path)
- out := &Lazy_Buffer{
- s = path,
- vol_and_path = original_path,
- vol_len = vol_len,
- }
- defer lazy_buffer_destroy(out)
-
- r, dot_dot := 0, 0
- if rooted {
- lazy_buffer_append(out, SEPARATOR) or_return
- r, dot_dot = 1, 1
- }
-
- for r < n {
- switch {
- case is_separator(path[r]):
- r += 1
- case path[r] == '.' && (r+1 == n || is_separator(path[r+1])):
- r += 1
- case path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_separator(path[r+2])):
- r += 2
- switch {
- case out.w > dot_dot:
- out.w -= 1
- for out.w > dot_dot && !is_separator(lazy_buffer_index(out, out.w)) {
- out.w -= 1
- }
- case !rooted:
- if out.w > 0 {
- lazy_buffer_append(out, SEPARATOR) or_return
- }
- lazy_buffer_append(out, '.') or_return
- lazy_buffer_append(out, '.') or_return
- dot_dot = out.w
- }
- case:
- if rooted && out.w != 1 || !rooted && out.w != 0 {
- lazy_buffer_append(out, SEPARATOR) or_return
- }
- for ; r < n && !is_separator(path[r]); r += 1 {
- lazy_buffer_append(out, path[r]) or_return
- }
-
- }
- }
-
- if out.w == 0 {
- lazy_buffer_append(out, '.') or_return
- }
-
- s := lazy_buffer_string(out) or_return
-
- new_allocation: bool
- cleaned, new_allocation = from_slash(s)
- if new_allocation {
- delete(s)
- }
- return
-}
-
-// Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character.
-from_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
- if SEPARATOR == '/' {
- return path, false
- }
- return strings.replace_all(path, "/", SEPARATOR_STRING, allocator)
-}
-
-// Returns the result of replacing each OS specific separator with a forward slash `/` character.
-to_slash :: proc(path: string, allocator := context.allocator) -> (new_path: string, new_allocation: bool) {
- if SEPARATOR == '/' {
- return path, false
- }
- return strings.replace_all(path, SEPARATOR_STRING, "/", allocator)
-}
+/*
+Get the absolute path to `path` with respect to the process's current directory.
+*Allocates Using Provided Allocator*
+*/
+abs :: os.get_absolute_path
Relative_Error :: enum {
None,
-
Cannot_Relate,
}
@@ -390,8 +172,10 @@ Relative_Error :: enum {
*/
rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) {
context.allocator = allocator
- base_clean := clean(base_path, allocator)
- target_clean := clean(target_path, allocator)
+ base_clean, base_err := clean(base_path, allocator)
+ if base_err != nil { return "", .Cannot_Relate}
+ target_clean, target_err := clean(target_path, allocator)
+ if target_err != nil { return "", .Cannot_Relate}
defer delete(base_clean, allocator)
defer delete(target_clean, allocator)
@@ -472,7 +256,8 @@ dir :: proc(path: string, allocator := context.allocator) -> string {
for i >= len(vol) && !is_separator(path[i]) {
i -= 1
}
- dir := clean(path[len(vol) : i+1])
+ dir, dir_err := clean(path[len(vol) : i+1], allocator)
+ if dir_err != nil { return "" }
defer delete(dir)
if dir == "." && len(vol) > 2 {
return strings.clone(vol)
@@ -487,108 +272,4 @@ dir :: proc(path: string, allocator := context.allocator) -> string {
// An empty string returns nil. A non-empty string with no separators returns a 1-element array.
// Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`.
// Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}.
-split_list :: proc(path: string, allocator := context.allocator) -> (list: []string, err: runtime.Allocator_Error) #optional_allocator_error {
- if path == "" {
- return nil, nil
- }
-
- start: int
- quote: bool
-
- start, quote = 0, false
- count := 0
-
- for i := 0; i < len(path); i += 1 {
- c := path[i]
- switch {
- case c == '"':
- quote = !quote
- case c == LIST_SEPARATOR && !quote:
- count += 1
- }
- }
-
- start, quote = 0, false
- list = make([]string, count + 1, allocator) or_return
- index := 0
- for i := 0; i < len(path); i += 1 {
- c := path[i]
- switch {
- case c == '"':
- quote = !quote
- case c == LIST_SEPARATOR && !quote:
- list[index] = path[start:i]
- index += 1
- start = i + 1
- }
- }
- assert(index == count)
- list[index] = path[start:]
-
- for s0, i in list {
- s, new := strings.replace_all(s0, `"`, ``, allocator)
- if !new {
- s = strings.clone(s, allocator) or_return
- }
- list[i] = s
- }
-
- return list, nil
-}
-
-
-
-
-/*
- Lazy_Buffer is a lazily made path buffer
- When it does allocate, it uses the context.allocator
- */
-@(private)
-Lazy_Buffer :: struct {
- s: string,
- b: []byte,
- w: int, // write index
- vol_and_path: string,
- vol_len: int,
-}
-
-@(private)
-lazy_buffer_index :: proc(lb: ^Lazy_Buffer, i: int) -> byte {
- if lb.b != nil {
- return lb.b[i]
- }
- return lb.s[i]
-}
-@(private)
-lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) -> (err: runtime.Allocator_Error) {
- if lb.b == nil {
- if lb.w < len(lb.s) && lb.s[lb.w] == c {
- lb.w += 1
- return
- }
- lb.b = make([]byte, len(lb.s)) or_return
- copy(lb.b, lb.s[:lb.w])
- }
- lb.b[lb.w] = c
- lb.w += 1
- return
-}
-@(private)
-lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> (s: string, err: runtime.Allocator_Error) {
- if lb.b == nil {
- return strings.clone(lb.vol_and_path[:lb.vol_len+lb.w])
- }
-
- x := lb.vol_and_path[:lb.vol_len]
- y := string(lb.b[:lb.w])
- z := make([]byte, len(x)+len(y)) or_return
- copy(z, x)
- copy(z[len(x):], y)
- return string(z), nil
-}
-@(private)
-lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) -> runtime.Allocator_Error {
- err := delete(lb.b)
- lb^ = {}
- return err
-}
+split_list :: os.split_path_list
\ No newline at end of file
diff --git a/core/path/filepath/path_js.odin b/core/path/filepath/path_js.odin
index 3b5ac04f5..e2657cb3e 100644
--- a/core/path/filepath/path_js.odin
+++ b/core/path/filepath/path_js.odin
@@ -1,36 +1,5 @@
package filepath
-import "base:runtime"
-
-import "core:strings"
-
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
-LIST_SEPARATOR :: ':'
-
-is_reserved_name :: proc(path: string) -> bool {
- return false
-}
-
-is_abs :: proc(path: string) -> bool {
- return strings.has_prefix(path, "/")
-}
-
-abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
- if is_abs(path) {
- return strings.clone(string(path), allocator), true
- }
-
- return path, false
-}
-
-join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
- for e, i in elems {
- if e != "" {
- runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
- p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
- return clean(p, allocator)
- }
- }
- return "", nil
-}
\ No newline at end of file
+LIST_SEPARATOR :: ':'
\ No newline at end of file
diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin
index 8bf412599..2e1b1419e 100644
--- a/core/path/filepath/path_unix.odin
+++ b/core/path/filepath/path_unix.odin
@@ -1,46 +1,6 @@
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
package filepath
-import "base:runtime"
-
-import "core:strings"
-import "core:sys/posix"
-
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
-LIST_SEPARATOR :: ':'
-
-is_reserved_name :: proc(path: string) -> bool {
- return false
-}
-
-is_abs :: proc(path: string) -> bool {
- return strings.has_prefix(path, "/")
-}
-
-abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
- rel := path
- if rel == "" {
- rel = "."
- }
- rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator)
- path_ptr := posix.realpath(rel_cstr, nil)
- if path_ptr == nil {
- return "", posix.errno() == nil
- }
- defer posix.free(path_ptr)
-
- path_str := strings.clone(string(path_ptr), allocator)
- return path_str, true
-}
-
-join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
- for e, i in elems {
- if e != "" {
- runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
- p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
- return clean(p, allocator)
- }
- }
- return "", nil
-}
+LIST_SEPARATOR :: ':'
\ No newline at end of file
diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin
index 74cc6ca1e..e2657cb3e 100644
--- a/core/path/filepath/path_wasi.odin
+++ b/core/path/filepath/path_wasi.odin
@@ -1,36 +1,5 @@
package filepath
-import "base:runtime"
-
-import "core:strings"
-
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
-LIST_SEPARATOR :: ':'
-
-is_reserved_name :: proc(path: string) -> bool {
- return false
-}
-
-is_abs :: proc(path: string) -> bool {
- return strings.has_prefix(path, "/")
-}
-
-abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
- if is_abs(path) {
- return strings.clone(string(path), allocator), true
- }
-
- return path, false
-}
-
-join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
- for e, i in elems {
- if e != "" {
- runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
- p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
- return clean(p, allocator)
- }
- }
- return "", nil
-}
+LIST_SEPARATOR :: ':'
\ No newline at end of file
diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin
index d7549a42c..9b8e92bbd 100644
--- a/core/path/filepath/path_windows.odin
+++ b/core/path/filepath/path_windows.odin
@@ -1,135 +1,9 @@
package filepath
-import "core:strings"
-import "base:runtime"
-import "core:os"
-import win32 "core:sys/windows"
-
SEPARATOR :: '\\'
SEPARATOR_STRING :: `\`
LIST_SEPARATOR :: ';'
-@(private)
-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
-}
-
is_UNC :: proc(path: string) -> bool {
- return volume_name_len(path) > 2
-}
-
-is_abs :: proc(path: string) -> bool {
- if is_reserved_name(path) {
- return true
- }
- if len(path) > 0 && is_slash(path[0]) {
- return true
- }
- l := volume_name_len(path)
- if l == 0 {
- return false
- }
-
- path := path
- path = path[l:]
- if path == "" {
- return false
- }
- return is_slash(path[0])
-}
-
-@(private)
-temp_full_path :: proc(name: string) -> (path: string, err: os.Error) {
- ta := context.temp_allocator
-
- name := name
- if name == "" {
- name = "."
- }
-
- p := win32.utf8_to_utf16(name, ta)
- n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil)
- if n == 0 {
- return "", os.get_last_error()
- }
-
- buf := make([]u16, n, ta)
- n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil)
- if n == 0 {
- delete(buf)
- return "", os.get_last_error()
- }
-
- return win32.utf16_to_utf8(buf[:n], ta)
-}
-
-abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
- runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
- full_path, err := temp_full_path(path)
- if err != nil {
- return "", false
- }
- p := clean(full_path, allocator)
- return p, true
-}
-
-join :: proc(elems: []string, allocator := context.allocator) -> (string, runtime.Allocator_Error) #optional_allocator_error {
- for e, i in elems {
- if e != "" {
- return join_non_empty(elems[i:], allocator)
- }
- }
- return "", nil
-}
-
-join_non_empty :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) {
- context.allocator = allocator
-
- runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator)
-
- if len(elems[0]) == 2 && elems[0][1] == ':' {
- i := 1
- for ; i < len(elems); i += 1 {
- if elems[i] != "" {
- break
- }
- }
- s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
- s = strings.concatenate({elems[0], s}, context.temp_allocator) or_return
- return clean(s)
- }
-
- p := strings.join(elems, SEPARATOR_STRING, context.temp_allocator) or_return
- p = clean(p) or_return
- if !is_UNC(p) {
- return p, nil
- }
-
- head := clean(elems[0], context.temp_allocator) or_return
- if is_UNC(head) {
- return p, nil
- }
- delete(p) // It is not needed now
-
- tail := strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator) or_return
- tail = clean(tail, context.temp_allocator) or_return
- if head[len(head)-1] == SEPARATOR {
- return strings.concatenate({head, tail})
- }
-
- return strings.concatenate({head, SEPARATOR_STRING, tail})
-}
+ return len(volume_name(path)) > 2
+}
\ No newline at end of file
diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin
index 05d67daf0..2e2a4ff54 100644
--- a/core/path/filepath/walk.odin
+++ b/core/path/filepath/walk.odin
@@ -3,81 +3,103 @@
package filepath
import "core:os"
-import "core:slice"
-// Walk_Proc is the type of the procedure called for each file or directory visited by 'walk'
-// The 'path' parameter contains the parameter to walk as a prefix (this is the same as info.fullpath except on 'root')
-// The 'info' parameter is the os.File_Info for the named path
-//
-// If there was a problem walking to the file or directory named by path, the incoming error will describe the problem
-// and the procedure can decide how to handle that error (and walk will not descend into that directory)
-// In the case of an error, the info argument will be 0
-// If an error is returned, processing stops
-// The sole exception is if 'skip_dir' is returned as true:
-// when 'skip_dir' is invoked on a directory. 'walk' skips directory contents
-// when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory
-Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool)
+Walker :: os.Walker
-// walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root'
-// All errors that happen visiting files and directories are filtered by walk_proc
-// The files are walked in lexical order to make the output deterministic
-// NOTE: Walking large directories can be inefficient due to the lexical sort
-// NOTE: walk does not follow symbolic links
-// NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done
-walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error {
- info, err := os.lstat(root, context.temp_allocator)
- defer os.file_info_delete(info, context.temp_allocator)
+/*
+Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
- skip_dir: bool
- if err != nil {
- err, skip_dir = walk_proc(info, err, user_data)
- } else {
- err, skip_dir = _walk(info, walk_proc, user_data)
- }
- return nil if skip_dir else err
-}
+You are allowed to repeatedly call this to reuse it for later walks.
+For an example on how to use the walker, see `walker_walk`.
+*/
+walker_init :: os.walker_init
-@(private)
-_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) {
- if !info.is_dir {
- if info.fullpath == "" && info.name == "" {
- // ignore empty things
- return
- }
- return walk_proc(info, nil, user_data)
- }
+/*
+Creates a walker, either using a path or a file pointer to a directory the walker will start at.
- fis: []os.File_Info
- err1: os.Error
- fis, err = read_dir(info.fullpath, context.temp_allocator)
- defer os.file_info_slice_delete(fis, context.temp_allocator)
+For an example on how to use the walker, see `walker_walk`.
+*/
+walker_create :: os.walker_create
- err1, skip_dir = walk_proc(info, err, user_data)
- if err != nil || err1 != nil || skip_dir {
- err = err1
- return
- }
+/*
+Returns the last error that occurred during the walker's operations.
- for fi in fis {
- err, skip_dir = _walk(fi, walk_proc, user_data)
- if err != nil || skip_dir {
- if !fi.is_dir || !skip_dir {
- return
+Can be called while iterating, or only at the end to check if anything failed.
+*/
+walker_error :: os.walker_error
+
+walker_destroy :: os.walker_destroy
+
+// Marks the current directory to be skipped (not entered into).
+walker_skip_dir :: os.walker_skip_dir
+
+/*
+Returns the next file info in the iterator, files are iterated in breadth-first order.
+
+If an error occurred opening a directory, you may get zero'd info struct and
+`walker_error` will return the error.
+
+Example:
+ package main
+
+ import "core:fmt"
+ import "core:strings"
+ import "core:os"
+
+ main :: proc() {
+ w := os.walker_create("core")
+ defer os.walker_destroy(&w)
+
+ for info in os.walker_walk(&w) {
+ // Optionally break on the first error:
+ // _ = walker_error(&w) or_break
+
+ // Or, handle error as we go:
+ if path, err := os.walker_error(&w); err != nil {
+ fmt.eprintfln("failed walking %s: %s", path, err)
+ continue
}
+
+ // Or, do not handle errors during iteration, and just check the error at the end.
+
+
+
+ // Skip a directory:
+ if strings.has_suffix(info.fullpath, ".git") {
+ os.walker_skip_dir(&w)
+ continue
+ }
+
+ fmt.printfln("%#v", info)
+ }
+
+ // Handle error if one happened during iteration at the end:
+ if path, err := os.walker_error(&w); err != nil {
+ fmt.eprintfln("failed walking %s: %v", path, err)
}
}
+*/
+walker_walk :: os.walker_walk
- return
-}
+/*
+ Reads the file `f` (assuming it is a directory) and returns the unsorted directory entries.
+ This returns up to `n` entries OR all of them if `n <= 0`.
+*/
+read_directory :: os.read_directory
-@(private)
-read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) {
- f := os.open(dir_name, os.O_RDONLY) or_return
- defer os.close(f)
- fis = os.read_dir(f, -1, allocator) or_return
- slice.sort_by(fis, proc(a, b: os.File_Info) -> bool {
- return a.name < b.name
- })
- return
-}
+/*
+ Reads the file `f` (assuming it is a directory) and returns all of the unsorted directory entries.
+*/
+read_all_directory :: os.read_all_directory
+
+/*
+ Reads the named directory by path (assuming it is a directory) and returns the unsorted directory entries.
+ This returns up to `n` entries OR all of them if `n <= 0`.
+*/
+read_directory_by_path :: os.read_directory_by_path
+
+/*
+ Reads the named directory by path (assuming it is a directory) and returns all of the unsorted directory entries.
+*/
+read_all_directory_by_path :: os.read_all_directory_by_path
\ No newline at end of file
diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin
index dc53dc3dc..281eaa82d 100644
--- a/core/prof/spall/spall.odin
+++ b/core/prof/spall/spall.odin
@@ -1,8 +1,9 @@
package spall
+import "base:intrinsics"
+
import "core:os"
import "core:time"
-import "base:intrinsics"
// File Format
@@ -64,7 +65,9 @@ NAME_EVENT_MAX :: size_of(Name_Event) + 255
Context :: struct {
precise_time: bool,
timestamp_scale: f64,
- fd: os.Handle,
+
+ file: ^os.File,
+ fd: uintptr,
}
Buffer :: struct {
@@ -79,18 +82,20 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000
context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok {
- fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600)
+ file, err := os.open(filename, {.Write, .Append, .Create, .Trunc}, {.Read_User, .Write_User})
if err != nil {
return
}
- ctx.fd = fd
+ ctx.file = file
+ ctx.fd = os.fd(file)
+
ctx.precise_time = precise_time
ctx.timestamp_scale = timestamp_scale
temp := [size_of(Manual_Stream_Header)]u8{}
_build_stream_header(temp[:], ctx.timestamp_scale)
- os.write(ctx.fd, temp[:])
+ write(ctx.fd, temp[:])
ok = true
return
}
@@ -109,7 +114,7 @@ context_destroy :: proc(ctx: ^Context) {
return
}
- os.close(ctx.fd)
+ os.close(ctx.file)
ctx^ = Context{}
}
@@ -288,12 +293,12 @@ _buffer_name_process :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name
buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Process)
}
-@(no_instrumentation)
-write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) {
- return _write(fd, buf)
+@(no_instrumentation, private="package")
+write :: proc "contextless" (fd: uintptr, buf: []byte) {
+ _write(fd, buf)
}
-@(no_instrumentation)
+@(no_instrumentation, private="package")
tick_now :: proc "contextless" () -> (ns: i64) {
return _tick_now()
}
diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin
index 8060af448..207bd8471 100644
--- a/core/prof/spall/spall_linux.odin
+++ b/core/prof/spall/spall_linux.odin
@@ -1,19 +1,17 @@
#+private
package spall
-// Only for types and constants.
-import "core:os"
-
// Package is `#+no-instrumentation`, safe to use.
import "core:sys/linux"
MAX_RW :: 0x7fffffff
@(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
+_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ {
+ n: int
for n < len(data) {
chunk := data[:min(len(data), MAX_RW)]
- n += linux.write(linux.Fd(fd), chunk) or_return
+ n += linux.write(linux.Fd(fd), chunk) or_break
}
return
}
diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin
index 455245aad..2f1e356ee 100644
--- a/core/prof/spall/spall_unix.odin
+++ b/core/prof/spall/spall_unix.odin
@@ -2,29 +2,23 @@
#+build darwin, freebsd, openbsd, netbsd
package spall
-// Only for types.
-import "core:os"
-
import "core:sys/posix"
MAX_RW :: 0x7fffffff
@(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
- if len(data) == 0 {
- return 0, nil
- }
-
+_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ {
+ n: int
for n < len(data) {
chunk := data[:min(len(data), MAX_RW)]
written := posix.write(posix.FD(fd), raw_data(chunk), len(chunk))
if written < 0 {
- return n, os.get_last_error()
+ return
}
n += written
}
- return n, nil
+ return
}
// NOTE(tetra): "RAW" means: Not adjusted by NTP.
diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin
index 11e216b63..2d059dc4d 100644
--- a/core/prof/spall/spall_windows.odin
+++ b/core/prof/spall/spall_windows.odin
@@ -1,20 +1,13 @@
#+private
package spall
-// Only for types.
-import "core:os"
-
// Package is `#+no-instrumentation`, safe to use.
import win32 "core:sys/windows"
MAX_RW :: 1<<30
@(no_instrumentation)
-_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ {
- if len(data) == 0 {
- return 0, nil
- }
-
+_write :: proc "contextless" (fd: uintptr, data: []byte) #no_bounds_check /* bounds check would segfault instrumentation */ {
single_write_length: win32.DWORD
total_write: i64
length := i64(len(data))
@@ -25,11 +18,12 @@ _write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #n
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), os.get_last_error()
+ return
}
total_write += i64(single_write_length)
}
- return int(total_write), nil
+
+ return
}
@(no_instrumentation)
diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin
index 392761e1d..6e9c8ab91 100644
--- a/core/sys/linux/sys.odin
+++ b/core/sys/linux/sys.odin
@@ -2616,9 +2616,9 @@ futex :: proc{
If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument.
Otherwise use an array of integers.
*/
-sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) {
+sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (int, Errno) {
ret := syscall(SYS_sched_setaffinity, pid, cpusetsize, mask)
- return Errno(-ret)
+ return errno_unwrap(ret, int)
}
/*
@@ -2628,9 +2628,9 @@ sched_setaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawpt
If you are running on a system with less than 128 cores you can use `linux.Cpu_Set` as the type for the mask argument.
Otherwise use an array of integers.
*/
-sched_getaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (Errno) {
+sched_getaffinity :: proc "contextless" (pid: Pid, cpusetsize: uint, mask: rawptr) -> (int, Errno) {
ret := syscall(SYS_sched_getaffinity, pid, cpusetsize, mask)
- return Errno(-ret)
+ return errno_unwrap(ret, int)
}
// TODO(flysand): set_thread_area
diff --git a/core/terminal/internal.odin b/core/terminal/internal_os.odin
similarity index 98%
rename from core/terminal/internal.odin
rename to core/terminal/internal_os.odin
index 47ed1818f..127cbae54 100644
--- a/core/terminal/internal.odin
+++ b/core/terminal/internal_os.odin
@@ -1,3 +1,5 @@
+#+build !freestanding
+#+build !js
#+private
package terminal
@@ -77,4 +79,4 @@ init_terminal :: proc "contextless" () {
@(fini)
fini_terminal :: proc "contextless" () {
_fini_terminal()
-}
+}
\ No newline at end of file
diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin
index 37fdaff36..86d928649 100644
--- a/core/terminal/terminal.odin
+++ b/core/terminal/terminal.odin
@@ -1,8 +1,6 @@
// Interaction with the command line interface (`CLI`) of the system.
package terminal
-import "core:os"
-
/*
This describes the range of colors that a terminal is capable of supporting.
*/
@@ -15,14 +13,14 @@ Color_Depth :: enum {
}
/*
-Returns true if the file `handle` is attached to a terminal.
+Returns true if the `File` is attached to a terminal.
This is normally true for `os.stdout` and `os.stderr` unless they are
redirected to a file.
*/
@(require_results)
-is_terminal :: proc(handle: os.Handle) -> bool {
- return _is_terminal(handle)
+is_terminal :: proc(f: $T) -> bool {
+ return _is_terminal(f)
}
/*
diff --git a/core/terminal/terminal_js.odin b/core/terminal/terminal_js.odin
index 4dcd4465e..78c6c240f 100644
--- a/core/terminal/terminal_js.odin
+++ b/core/terminal/terminal_js.odin
@@ -2,9 +2,7 @@
#+build js
package terminal
-import "core:os"
-
-_is_terminal :: proc "contextless" (handle: os.Handle) -> bool {
+_is_terminal :: proc "contextless" (handle: any) -> bool {
return true
}
diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin
index 8d96dd256..83e64c6d8 100644
--- a/core/terminal/terminal_posix.odin
+++ b/core/terminal/terminal_posix.odin
@@ -4,10 +4,9 @@ package terminal
import "base:runtime"
import "core:os"
-import "core:sys/posix"
-_is_terminal :: proc "contextless" (handle: os.Handle) -> bool {
- return bool(posix.isatty(posix.FD(handle)))
+_is_terminal :: proc "contextless" (f: ^os.File) -> bool {
+ return os.is_tty(f)
}
_init_terminal :: proc "contextless" () {
diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin
index 6d5f98a1f..6c77330b5 100644
--- a/core/terminal/terminal_windows.odin
+++ b/core/terminal/terminal_windows.odin
@@ -5,9 +5,8 @@ import "base:runtime"
import "core:os"
import "core:sys/windows"
-_is_terminal :: proc "contextless" (handle: os.Handle) -> bool {
- is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
- return is_tty
+_is_terminal :: proc "contextless" (f: ^os.File) -> bool {
+ return os.is_tty(f)
}
old_modes: [2]struct{
diff --git a/core/testing/runner.odin b/core/testing/runner.odin
index 03115b165..8f26aa6c6 100644
--- a/core/testing/runner.odin
+++ b/core/testing/runner.odin
@@ -10,24 +10,24 @@ package testing
Feoramund: Total rewrite.
*/
-import "base:intrinsics"
-import "base:runtime"
-import "core:bytes"
-@require import "core:encoding/base64"
-@require import "core:encoding/json"
-import "core:fmt"
-import "core:io"
-@require import "core:log"
-import "core:math/rand"
-import "core:mem"
-import "core:os"
-import "core:slice"
-@require import "core:strings"
-import "core:sync/chan"
-import "core:terminal"
-import "core:terminal/ansi"
-import "core:thread"
-import "core:time"
+import "base:intrinsics"
+import "base:runtime"
+import "core:bytes"
+@(require) import "core:encoding/base64"
+@(require) import "core:encoding/json"
+import "core:fmt"
+import "core:io"
+@(require) import "core:log"
+import "core:math/rand"
+import "core:mem"
+import "core:os"
+import "core:slice"
+@(require) import "core:strings"
+import "core:sync/chan"
+import "core:terminal"
+import "core:terminal/ansi"
+import "core:thread"
+import "core:time"
// Specify how many threads to use when running tests.
TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0)
@@ -219,8 +219,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
}
}
- stdout := io.to_writer(os.stream_from_handle(os.stdout))
- stderr := io.to_writer(os.stream_from_handle(os.stderr))
+ stdout := os.to_stream(os.stdout)
+ stderr := os.to_stream(os.stderr)
// The animations are only ever shown through STDOUT;
// STDERR is used exclusively for logging regardless of error level.
@@ -317,7 +317,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// -- Set thread count.
when TEST_THREADS == 0 {
- thread_count := os.processor_core_count()
+ thread_count := os.get_processor_core_count()
} else {
thread_count := max(1, TEST_THREADS)
}
diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin
index 74608bb48..fb19a0115 100644
--- a/core/testing/signal_handler_libc.odin
+++ b/core/testing/signal_handler_libc.odin
@@ -91,7 +91,7 @@ stop_test_callback :: proc "c" (sig: libc.int) {
advisory_a := `
The test runner's main thread has caught an unrecoverable error (signal `
advisory_b := `) and will now forcibly terminate.
-This is a dire bug and should be reported to the Odin developers.
+Unless you terminated the tests yourself, this could be a dire bug and should be reported to the Odin developers.
`
libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr)
libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr)
diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin
index a29fdc003..b0e3dae67 100644
--- a/core/text/i18n/gettext.odin
+++ b/core/text/i18n/gettext.odin
@@ -14,7 +14,6 @@ package i18n
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
-import "core:os"
import "core:strings"
import "core:bytes"
@@ -28,22 +27,17 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
return {}, .MO_File_Invalid
}
- /*
- Check magic. Should be 0x950412de in native Endianness.
- */
+ // Check magic. Should be 0x950412de in native Endianness.
native := true
magic := read_u32(data, native) or_return
if magic != 0x950412de {
native = false
- magic = read_u32(data, native) or_return
-
+ magic = read_u32(data, native) or_return
if magic != 0x950412de { return {}, .MO_File_Invalid_Signature }
}
- /*
- We can ignore version_minor at offset 6.
- */
+ // We can ignore version_minor at offset 6.
version_major := read_u16(data[4:]) or_return
if version_major > 1 { return {}, .MO_File_Unsupported_Version }
@@ -53,17 +47,13 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
if count == 0 { return {}, .Empty_Translation_Catalog }
- /*
- Initalize Translation, interner and optional pluralizer.
- */
+ // Initalize Translation, interner and optional pluralizer.
translation = new(Translation)
translation.pluralize = pluralizer
strings.intern_init(&translation.intern, allocator, allocator)
for n := u32(0); n < count; n += 1 {
- /*
- Grab string's original length and offset.
- */
+ // Grab string's original length and offset.
offset := original_offset + 8 * n
if len(data) < int(offset + 8) { return translation, .MO_File_Invalid }
@@ -82,9 +72,7 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
key_data := data[o_offset:][:o_length]
val_data := data[t_offset:][:t_length]
- /*
- Could be a pluralized string.
- */
+ // Could be a pluralized string.
zero := []byte{0}
keys := bytes.split(key_data, zero); defer delete(keys)
vals := bytes.split(val_data, zero); defer delete(vals)
@@ -137,22 +125,7 @@ parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, plur
return
}
-parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
- context.allocator = allocator
-
- data, data_ok := os.read_entire_file(filename)
- defer delete(data)
-
- if !data_ok { return {}, .File_Error }
-
- return parse_mo_from_bytes(data, options, pluralizer, allocator)
-}
-
-parse_mo :: proc { parse_mo_file, parse_mo_from_bytes }
-
-/*
- Helpers.
-*/
+@(private)
read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) {
if len(data) < size_of(u32) { return 0, .Premature_EOF }
@@ -169,6 +142,7 @@ read_u32 :: proc(data: []u8, native_endian := true) -> (res: u32, err: Error) {
}
}
+@(private)
read_u16 :: proc(data: []u8, native_endian := true) -> (res: u16, err: Error) {
if len(data) < size_of(u16) { return 0, .Premature_EOF }
diff --git a/core/text/i18n/i18_js.odin b/core/text/i18n/i18_js.odin
new file mode 100644
index 000000000..73e8535a5
--- /dev/null
+++ b/core/text/i18n/i18_js.odin
@@ -0,0 +1,16 @@
+#+build freestanding
+#+build js
+package i18n
+/*
+ Internationalization helpers.
+
+ Copyright 2021-2022 Jeroen van Rijn .
+ Made available under Odin's license.
+
+ List of contributors:
+ Jeroen van Rijn: Initial implementation.
+*/
+@(private)
+parse_qt :: proc { parse_qt_linguist_from_bytes }
+
+parse_mo :: proc { parse_mo_from_bytes }
\ No newline at end of file
diff --git a/core/text/i18n/i18n.odin b/core/text/i18n/i18n.odin
index b978bffc4..148fe229f 100644
--- a/core/text/i18n/i18n.odin
+++ b/core/text/i18n/i18n.odin
@@ -8,7 +8,7 @@ package i18n
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
-import "core:strings"
+import "core:strings"
// Currently active catalog.
ACTIVE: ^Translation
diff --git a/core/text/i18n/i18n_os.odin b/core/text/i18n/i18n_os.odin
new file mode 100644
index 000000000..7a7995612
--- /dev/null
+++ b/core/text/i18n/i18n_os.odin
@@ -0,0 +1,38 @@
+#+build !freestanding
+#+build !js
+package i18n
+/*
+ Internationalization helpers.
+
+ Copyright 2021-2022 Jeroen van Rijn .
+ Made available under Odin's license.
+
+ List of contributors:
+ Jeroen van Rijn: Initial implementation.
+*/
+import "base:runtime"
+import "core:os"
+
+@(private)
+read_file :: proc(filename: string, allocator: runtime.Allocator) -> (data: []u8, err: Error) {
+ file_data, file_err := os.read_entire_file(filename, allocator)
+ if file_err != nil {
+ return {}, .File_Error
+ }
+ return file_data, nil
+}
+
+parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
+ data := read_file(filename, allocator) or_return
+ return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator)
+}
+
+parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes }
+
+parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
+ data := read_file(filename, allocator) or_return
+ defer delete(data)
+ return parse_mo_from_bytes(data, options, pluralizer, allocator)
+}
+
+parse_mo :: proc { parse_mo_file, parse_mo_from_bytes }
\ No newline at end of file
diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin
index 2fc5efe7b..9c5791b67 100644
--- a/core/text/i18n/qt_linguist.odin
+++ b/core/text/i18n/qt_linguist.odin
@@ -11,7 +11,6 @@ package i18n
List of contributors:
Jeroen van Rijn: Initial implementation.
*/
-import "core:os"
import "core:encoding/xml"
import "core:strings"
@@ -56,9 +55,7 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
return nil, .TS_File_Parse_Error
}
- /*
- Initalize Translation, interner and optional pluralizer.
- */
+ // Initalize Translation, interner and optional pluralizer.
translation = new(Translation)
translation.pluralize = pluralizer
strings.intern_init(&translation.intern, allocator, allocator)
@@ -69,7 +66,6 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
child_id := get_id(value) or_return
// These should be s.
-
if ts.elements[child_id].ident != "context" {
return translation, .TS_File_Expected_Context
}
@@ -158,13 +154,3 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI
return
}
-parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
- context.allocator = allocator
-
- data, data_ok := os.read_entire_file(filename)
- if !data_ok { return {}, .File_Error }
-
- return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator)
-}
-
-parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes }
\ No newline at end of file
diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin
index 24b44833f..cfbbe53ba 100644
--- a/core/text/regex/common/common.odin
+++ b/core/text/regex/common/common.odin
@@ -14,6 +14,8 @@ MAX_CAPTURE_GROUPS :: max(#config(ODIN_REGEX_MAX_CAPTURE_GROUPS, 10), 10)
MAX_PROGRAM_SIZE :: int(max(i16))
MAX_CLASSES :: int(max(u8))
+ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false)
+
Flag :: enum u8 {
// Multiline: treat `^` and `$` as if they also match newlines.
Multiline,
diff --git a/core/text/regex/common/debugging.odin b/core/text/regex/common/debugging.odin
index 1a241e136..055bbd20d 100644
--- a/core/text/regex/common/debugging.odin
+++ b/core/text/regex/common/debugging.odin
@@ -8,16 +8,9 @@ package regex_common
Feoramund: Initial implementation.
*/
-@require import "core:os"
import "core:io"
import "core:strings"
-ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false)
-
-when ODIN_DEBUG_REGEX {
- debug_stream := os.stream_from_handle(os.stderr)
-}
-
write_padded_hex :: proc(w: io.Writer, #any_int n, zeroes: int) {
sb := strings.builder_make()
defer strings.builder_destroy(&sb)
diff --git a/core/text/regex/common/os.odin b/core/text/regex/common/os.odin
new file mode 100644
index 000000000..bde57f77f
--- /dev/null
+++ b/core/text/regex/common/os.odin
@@ -0,0 +1,17 @@
+#+build !freestanding
+#+build !js
+package regex_common
+
+/*
+ (c) Copyright 2024 Feoramund .
+ Made available under Odin's license.
+
+ List of contributors:
+ Feoramund: Initial implementation.
+*/
+
+@require import "core:os"
+
+when ODIN_DEBUG_REGEX {
+ debug_stream := os.stderr.stream
+}
\ No newline at end of file
diff --git a/core/text/table/doc.odin b/core/text/table/doc.odin
index 4b8d76893..d91763661 100644
--- a/core/text/table/doc.odin
+++ b/core/text/table/doc.odin
@@ -195,6 +195,7 @@ Example:
scripts :: proc(w: io.Writer) {
t: table.Table
table.init(&t)
+ defer table.destroy(&t)
table.caption(&t, "Tést Suite")
table.padding(&t, 1, 3)
table.header_of_aligned_values(&t, {{.Left, "Script"}, {.Center, "Sample"}})
@@ -224,9 +225,7 @@ Example:
}
main :: proc() {
- stdout := os.stream_from_handle(os.stdout)
-
- scripts(stdout)
+ scripts(os.to_stream(os.stdout))
}
Output:
@@ -274,6 +273,7 @@ Example:
box_drawing :: proc(w: io.Writer) {
t: table.Table
table.init(&t)
+ defer table.destroy(&t)
table.caption(&t, "Box Drawing Example")
table.padding(&t, 2, 2)
table.header_of_aligned_values(&t, {{.Left, "Operating System"}, {.Center, "Year Introduced"}})
@@ -299,9 +299,7 @@ Example:
}
main :: proc() {
- stdout := os.stream_from_handle(os.stdout)
-
- box_drawing(stdout)
+ box_drawing(os.to_stream(os.stdout))
}
While the decorations support multi-codepoint Unicode graphemes, do note that
diff --git a/core/text/table/utility.odin b/core/text/table/utility.odin
index 0e56fd968..675fa6b10 100644
--- a/core/text/table/utility.odin
+++ b/core/text/table/utility.odin
@@ -1,3 +1,5 @@
+#+build !freestanding
+#+build !js
package text_table
import "core:io"
@@ -5,7 +7,7 @@ import "core:os"
import "core:strings"
stdio_writer :: proc() -> io.Writer {
- return io.to_writer(os.stream_from_handle(os.stdout))
+ return os.to_stream(os.stdout)
}
strings_builder_writer :: proc(b: ^strings.Builder) -> io.Writer {
diff --git a/core/time/timezone/tz_os.odin b/core/time/timezone/tz_os.odin
new file mode 100644
index 000000000..fae4980c3
--- /dev/null
+++ b/core/time/timezone/tz_os.odin
@@ -0,0 +1,19 @@
+#+build !freestanding
+#+build !js
+package timezone
+
+import "core:os"
+import "core:time/datetime"
+
+load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) {
+ tzif_data, tzif_err := os.read_entire_file(filename, allocator)
+ if tzif_err != nil {
+ return nil, false
+ }
+ defer delete(tzif_data, allocator)
+ return parse_tzif(tzif_data, region_name, allocator)
+}
+
+region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) {
+ return load_tzif_file(file_path, reg, allocator)
+}
\ No newline at end of file
diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin
index 542e5c4f2..3939b3265 100644
--- a/core/time/timezone/tz_unix.odin
+++ b/core/time/timezone/tz_unix.odin
@@ -4,17 +4,17 @@ package timezone
import "core:os"
import "core:strings"
-import "core:path/filepath"
import "core:time/datetime"
+import "core:path/filepath"
local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) {
local_str, ok := os.lookup_env("TZ", allocator)
if !ok {
orig_localtime_path := "/etc/localtime"
- path, err := os.absolute_path_from_relative(orig_localtime_path, allocator)
+ path, err := os.get_absolute_path(orig_localtime_path, allocator)
if err != nil {
// If we can't find /etc/localtime, fallback to UTC
- if err == .ENOENT {
+ if err == .Not_Exist {
str, err2 := strings.clone("UTC", allocator)
if err2 != nil { return }
return str, true
@@ -28,16 +28,20 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success:
// This is a hackaround, because FreeBSD copies rather than softlinks their local timezone file,
// *sometimes* and then stores the original name of the timezone in /var/db/zoneinfo instead
if path == orig_localtime_path {
- data := os.read_entire_file("/var/db/zoneinfo", allocator) or_return
+ data, data_err := os.read_entire_file("/var/db/zoneinfo", allocator)
+ if data_err != nil {
+ return "", false
+ }
return strings.trim_right_space(string(data)), true
}
// Looking for tz path (ex fmt: "UTC", "Etc/UTC" or "America/Los_Angeles")
- path_dir, path_file := filepath.split(path)
+ path_dir, path_file := os.split_path(path)
+
if path_dir == "" {
return
}
- upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)-1])
+ upper_path_dir, upper_path_chunk := os.split_path(path_dir[:len(path_dir)])
if upper_path_dir == "" {
return
}
@@ -47,8 +51,8 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success:
if err != nil { return }
return region_str, true
} else {
- region_str, err := filepath.join({upper_path_chunk, path_file}, allocator = allocator)
- if err != nil { return }
+ region_str, region_str_err := os.join_path({upper_path_chunk, path_file}, allocator = allocator)
+ if region_str_err != nil { return }
return region_str, true
}
}
@@ -85,9 +89,10 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r
defer if tzdir_ok { delete(tzdir_str, allocator) }
if tzdir_ok {
- region_path := filepath.join({tzdir_str, reg_str}, allocator)
+ region_path, err := filepath.join({tzdir_str, reg_str}, allocator)
+ if err != nil { return nil, false }
defer delete(region_path, allocator)
-
+
if tz_reg, ok := load_tzif_file(region_path, reg_str, allocator); ok {
return tz_reg, true
}
@@ -95,7 +100,8 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r
db_paths := []string{"/usr/share/zoneinfo", "/share/zoneinfo", "/etc/zoneinfo"}
for db_path in db_paths {
- region_path := filepath.join({db_path, reg_str}, allocator)
+ region_path, err := filepath.join({db_path, reg_str}, allocator)
+ if err != nil { return nil, false}
defer delete(region_path, allocator)
if tz_reg, ok := load_tzif_file(region_path, reg_str, allocator); ok {
@@ -104,4 +110,4 @@ _region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_r
}
return nil, false
-}
+}
\ No newline at end of file
diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin
index f01553573..29e8ad25c 100644
--- a/core/time/timezone/tzdate.odin
+++ b/core/time/timezone/tzdate.odin
@@ -10,10 +10,6 @@ region_load :: proc(reg: string, allocator := context.allocator) -> (out_reg: ^
return _region_load(reg, allocator)
}
-region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) {
- return load_tzif_file(file_path, reg, allocator)
-}
-
region_load_from_buffer :: proc(buffer: []u8, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) {
return parse_tzif(buffer, reg, allocator)
}
diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin
index 804211ef4..3b92364ac 100644
--- a/core/time/timezone/tzif.odin
+++ b/core/time/timezone/tzif.odin
@@ -1,12 +1,10 @@
package timezone
-import "base:intrinsics"
-
-import "core:slice"
-import "core:strings"
-import "core:os"
-import "core:strconv"
-import "core:time/datetime"
+import "base:intrinsics"
+import "core:slice"
+import "core:strings"
+import "core:strconv"
+import "core:time/datetime"
// Implementing RFC8536 [https://datatracker.ietf.org/doc/html/rfc8536]
@@ -68,13 +66,6 @@ tzif_data_block_size :: proc(hdr: ^TZif_Header, version: TZif_Version) -> (block
int(hdr.isutcnt), true
}
-
-load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) {
- tzif_data := os.read_entire_file_from_filename(filename, allocator) or_return
- defer delete(tzif_data, allocator)
- return parse_tzif(tzif_data, region_name, allocator)
-}
-
@private
is_alphabetic :: proc(ch: u8) -> bool {
// ('A' -> 'Z') || ('a' -> 'z')
diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin
index 9517b632b..54f73370c 100644
--- a/core/unicode/tools/generate_entity_table.odin
+++ b/core/unicode/tools/generate_entity_table.odin
@@ -1,17 +1,14 @@
package xml_example
-import "core:encoding/xml"
-import "core:os"
+import "core:encoding/xml"
+import "core:os"
import path "core:path/filepath"
-import "core:mem"
-import "core:strings"
-import "core:strconv"
-import "core:slice"
-import "core:fmt"
+import "core:strings"
+import "core:strconv"
+import "core:slice"
+import "core:fmt"
-/*
- Silent error handler for the parser.
-*/
+// Silent error handler for the parser.
Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {}
OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", }
@@ -22,7 +19,7 @@ Entity :: struct {
description: string,
}
-generate_encoding_entity_table :: proc() {
+main :: proc() {
filename := path.join({ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml"})
defer delete(filename)
@@ -33,14 +30,14 @@ generate_encoding_entity_table :: proc() {
defer xml.destroy(doc)
if err != .None {
- fmt.printf("Load/Parse error: %v\n", err)
+ fmt.printfln("Load/Parse error: %v", err)
if err == .File_Error {
- fmt.printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename)
+ fmt.eprintfln("%q not found. Did you run \"tests\\download_assets.py\"?", filename)
}
os.exit(1)
}
- fmt.printf("\"%v\" loaded and parsed.\n", filename)
+ fmt.printfln("%q loaded and parsed.", filename)
generated_buf: strings.Builder
defer strings.builder_destroy(&generated_buf)
@@ -54,7 +51,7 @@ generate_encoding_entity_table :: proc() {
charlist := doc.elements[charlist_id]
- fmt.printf("Found `` with %v children.\n", len(charlist.value))
+ fmt.printfln("Found `` with %v children.", len(charlist.value))
entity_map: map[string]Entity
defer delete(entity_map)
@@ -73,7 +70,7 @@ generate_encoding_entity_table :: proc() {
char := doc.elements[id]
if char.ident != "character" {
- fmt.eprintf("Expected ``, got `<%v>`\n", char.ident)
+ fmt.eprintfln("Expected ``, got `<%v>`", char.ident)
os.exit(1)
}
@@ -90,15 +87,13 @@ generate_encoding_entity_table :: proc() {
}
desc, desc_ok := xml.find_child_by_ident(doc, id, "description")
+ assert(desc_ok)
description := ""
if len(doc.elements[desc].value) == 1 {
description = doc.elements[desc].value[0].(string)
}
- /*
- For us to be interested in this codepoint, it has to have at least one entity.
- */
-
+ // For us to be interested in this codepoint, it has to have at least one entity.
nth := 0
for {
character_entity := xml.find_child_by_ident(doc, id, "entity", nth) or_break
@@ -112,8 +107,8 @@ generate_encoding_entity_table :: proc() {
}
if name == "\"\"" {
- fmt.printf("%#v\n", char)
- fmt.printf("%#v\n", character_entity)
+ fmt.printfln("%#v", char)
+ fmt.printfln("%#v", character_entity)
}
if len(name) > max_name_length { longest_name = name }
@@ -139,18 +134,14 @@ generate_encoding_entity_table :: proc() {
}
}
- /*
- Sort by name.
- */
+ // Sort by name.
slice.sort(names[:])
- fmt.printf("Found %v unique `&name;` -> rune mappings.\n", count)
- fmt.printf("Shortest name: %v (%v)\n", shortest_name, min_name_length)
- fmt.printf("Longest name: %v (%v)\n", longest_name, max_name_length)
+ fmt.printfln("Found %v unique `&name;` -> rune mappings.", count)
+ fmt.printfln("Shortest name: %v (%v)", shortest_name, min_name_length)
+ fmt.printfln("Longest name: %v (%v)", longest_name, max_name_length)
- /*
- Generate table.
- */
+ // Generate table.
fmt.wprintln(w, "package encoding_unicode_entity")
fmt.wprintln(w, "")
fmt.wprintln(w, GENERATED)
@@ -158,10 +149,10 @@ generate_encoding_entity_table :: proc() {
fmt.wprintf (w, TABLE_FILE_PROLOG)
fmt.wprintln(w, "")
- fmt.wprintf (w, "// `&%v;`\n", shortest_name)
- fmt.wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length)
- fmt.wprintf (w, "// `&%v;`\n", longest_name)
- fmt.wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length)
+ fmt.wprintfln(w, "// `&%v;`", shortest_name)
+ fmt.wprintfln(w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v", min_name_length)
+ fmt.wprintfln(w, "// `&%v;`", longest_name)
+ fmt.wprintfln(w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v", max_name_length)
fmt.wprintln(w, "")
fmt.wprintln(w,
@@ -198,7 +189,7 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count:
}
prefix = rune(v[0])
- fmt.wprintf (w, "\tcase '%v':\n", prefix)
+ fmt.wprintfln(w, "\tcase '%v':", prefix)
fmt.wprintln(w, "\t\tswitch name {")
}
@@ -214,7 +205,6 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count:
} else {
fmt.wprintf(w, "\t\t\treturn {{%q, 0}}, 1, true\n", e.codepoints[0])
}
-
should_close = true
}
fmt.wprintln(w, "\t\t}")
@@ -229,11 +219,12 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: [2]rune, rune_count:
written := os.write_entire_file(generated_filename, transmute([]byte)strings.to_string(generated_buf))
- if written {
- fmt.printf("Successfully written generated \"%v\".\n", generated_filename)
+ if written == nil {
+ fmt.printfln("Successfully written generated \"%v\".", generated_filename)
} else {
- fmt.printf("Failed to write generated \"%v\".\n", generated_filename)
+ fmt.printfln("Failed to write generated \"%v\".", generated_filename)
}
+ // Not a library, no need to clean up.
}
GENERATED :: `/*
@@ -274,20 +265,4 @@ is_dotted_name :: proc(name: string) -> (dotted: bool) {
if r == '.' { return true}
}
return false
-}
-
-main :: proc() {
- track: mem.Tracking_Allocator
- mem.tracking_allocator_init(&track, context.allocator)
- context.allocator = mem.tracking_allocator(&track)
-
- generate_encoding_entity_table()
-
- if len(track.allocation_map) > 0 {
- fmt.println()
- for _, v in track.allocation_map {
- fmt.printf("%v Leaked %v bytes.\n", v.location, v.size)
- }
- }
- fmt.println("Done and cleaned up!")
}
\ No newline at end of file
diff --git a/examples/all/all_js.odin b/examples/all/all_js.odin
index ee006ae86..bb7ed7fa1 100644
--- a/examples/all/all_js.odin
+++ b/examples/all/all_js.odin
@@ -10,7 +10,7 @@ package all
@(require) import "core:compress"
@(require) import "core:compress/shoco"
-@(require) import "core:compress/gzip"
+// @(require) import "core:compress/gzip"
@(require) import "core:compress/zlib"
@(require) import "core:container/avl"
@@ -99,13 +99,13 @@ package all
@(require) import "core:mem"
@(require) import "core:mem/tlsf"
-@(require) import "core:mem/virtual"
+// Not supported on JS
+// @(require) import "core:mem/virtual"
@(require) import "core:odin/ast"
@(require) import doc_format "core:odin/doc-format"
@(require) import "core:odin/tokenizer"
-@(require) import "core:os"
@(require) import "core:path/slashpath"
@(require) import "core:relative"
@@ -130,6 +130,7 @@ package all
@(require) import "core:text/match"
@(require) import "core:text/regex"
@(require) import "core:text/scanner"
+// Not supported on JS, uses `core:mem/virtual`.
@(require) import "core:text/table"
@(require) import "core:thread"
diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin
index c5f627653..3576fc027 100644
--- a/examples/all/all_main.odin
+++ b/examples/all/all_main.odin
@@ -117,7 +117,7 @@ package all
@(require) import "core:prof/spall"
@(require) import "core:os"
-@(require) import "core:os/os2"
+@(require) import "core:os/old"
@(require) import "core:path/slashpath"
@(require) import "core:path/filepath"
diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin
index a8f3e94f6..a4fee030c 100644
--- a/tests/core/encoding/hxa/test_core_hxa.odin
+++ b/tests/core/encoding/hxa/test_core_hxa.odin
@@ -1,6 +1,3 @@
-// Tests "core:encoding:hxa".
-// Must be run with `-collection:tests=` flag, e.g.
-// ./odin run tests/core/encoding/hxa/test_core_hxa.odin -out=tests/core/test_core_hxa -collection:tests=./tests
package test_core_hxa
import "core:encoding/hxa"
@@ -13,13 +10,11 @@ import "core:os"
@test
test_read :: proc(t: ^testing.T) {
- data, _ := os.read_entire_file(TEAPOT_PATH)
- // file, err := hxa.read_from_file(TEAPOT_PATH)
+ data, _ := os.read_entire_file(TEAPOT_PATH, context.allocator)
file, err := hxa.read(data)
file.backing = data
file.allocator = context.allocator
hxa.file_destroy(file)
- // fmt.printfln("%#v", file)
e :: hxa.Read_Error.None
testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e)
diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin
index 6e6c8152e..8c554c6b5 100644
--- a/tests/core/encoding/ini/test_core_ini.odin
+++ b/tests/core/encoding/ini/test_core_ini.odin
@@ -1,8 +1,7 @@
+#+feature dynamic-literals
package test_core_ini
-import "base:runtime"
import "core:encoding/ini"
-import "core:mem/virtual"
import "core:strings"
import "core:testing"
@@ -64,7 +63,7 @@ ini_to_string :: proc(t: ^testing.T) {
testing.expectf(
t,
- strings.contains(str, "[LEVEL]LOG = debug"),
+ strings.contains(str, "[LEVEL]\nLOG = debug"),
"Expected `ini.save_map_to_string` to return a string equal to \"[LEVEL]LOG = debug\", got %v",
str,
)
diff --git a/tests/core/flags/test_core_flags.odin b/tests/core/flags/test_core_flags.odin
index 0cfcf8e75..834f6b630 100644
--- a/tests/core/flags/test_core_flags.odin
+++ b/tests/core/flags/test_core_flags.odin
@@ -1,16 +1,16 @@
package test_core_flags
-import "base:runtime"
-import "core:bytes"
-import "core:flags"
-import "core:fmt"
-@require import "core:log"
-import "core:math"
-@require import "core:net"
-import "core:os"
-import "core:strings"
-import "core:testing"
-import "core:time/datetime"
+import "base:runtime"
+import "core:bytes"
+import "core:flags"
+import "core:fmt"
+@(require) import "core:log"
+import "core:math"
+@(require) import "core:net"
+import "core:os"
+import "core:strings"
+import "core:testing"
+import "core:time/datetime"
Custom_Data :: struct {
a: int,
@@ -1249,7 +1249,7 @@ test_os_handle :: proc(t: ^testing.T) {
test_data := "Hellope!"
W :: struct {
- outf: os.Handle `args:"file=cw"`,
+ outf: ^os.File `args:"file=cw"`,
}
w: W
@@ -1263,7 +1263,7 @@ test_os_handle :: proc(t: ^testing.T) {
os.write_string(w.outf, test_data)
R :: struct {
- inf: os.Handle `args:"file=r"`,
+ inf: ^os.File `args:"file=r"`,
}
r: R
@@ -1274,8 +1274,8 @@ test_os_handle :: proc(t: ^testing.T) {
return
}
defer os.close(r.inf)
- data, read_ok := os.read_entire_file_from_handle(r.inf, context.temp_allocator)
- testing.expect_value(t, read_ok, true)
+ data, read_err := os.read_entire_file(r.inf, context.temp_allocator)
+ testing.expect_value(t, read_err, nil)
file_contents_equal := 0 == bytes.compare(transmute([]u8)test_data, data)
testing.expectf(t, file_contents_equal, "expected file contents to be the same, got %v", data)
}
diff --git a/tests/core/io/test_core_io.odin b/tests/core/io/test_core_io.odin
index 10c9550cb..728771b1b 100644
--- a/tests/core/io/test_core_io.odin
+++ b/tests/core/io/test_core_io.odin
@@ -5,7 +5,6 @@ import "core:bytes"
import "core:io"
import "core:log"
import "core:os"
-import "core:os/os2"
import "core:strings"
import "core:testing"
@@ -552,12 +551,12 @@ test_os_file_stream :: proc(t: ^testing.T) {
TEMPORARY_FILENAME :: "test_core_io_os_file_stream"
- fd, open_err := os.open(TEMPORARY_FILENAME, os.O_RDWR | os.O_CREATE | os.O_TRUNC, 0o644)
+ fd, open_err := os.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc})
if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) {
return
}
-
- stream := os.stream_from_handle(fd)
+
+ stream := os.to_stream(fd)
bytes_written, write_err := io.write(stream, buf[:])
if !testing.expectf(t, bytes_written == len(buf) && write_err == nil,
@@ -571,44 +570,7 @@ test_os_file_stream :: proc(t: ^testing.T) {
return
}
- results, _ := _test_stream(t, stream, buf[:])
-
- log.debugf("%#v", results)
-}
-
-@test
-test_os2_file_stream :: proc(t: ^testing.T) {
- defer if !testing.failed(t) {
- testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil)
- }
-
- buf: [32]u8
- for i in 0.. != len_buf<%v>, %v", bytes_written, len(buf), write_err) {
- return
- }
-
- flush_err := io.flush(stream)
- if !testing.expectf(t, flush_err == nil,
- "failed to Flush initial buffer: %v", write_err) {
- return
- }
-
- // os2 file stream proc close and destroy are the same.
+ // os file stream proc close and destroy are the same.
results, _ := _test_stream(t, stream, buf[:], do_destroy = false)
log.debugf("%#v", results)
@@ -676,10 +638,10 @@ test_bufio_buffered_reader :: proc(t: ^testing.T) {
@test
test_bufio_buffered_read_writer :: proc(t: ^testing.T) {
- // Using an os2.File as the backing stream for both reader & writer.
+ // Using an os.File as the backing stream for both reader & writer.
defer if !testing.failed(t) {
- testing.expect_value(t, os2.remove(TEMPORARY_FILENAME), nil)
+ testing.expect_value(t, os.remove(TEMPORARY_FILENAME), nil)
}
buf: [32]u8
@@ -687,15 +649,15 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) {
buf[i] = 'A' + i
}
- TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os2_file_stream"
+ TEMPORARY_FILENAME :: "test_core_io_bufio_read_writer_os_file_stream"
- fd, open_err := os2.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc})
+ fd, open_err := os.open(TEMPORARY_FILENAME, {.Read, .Write, .Create, .Trunc})
if !testing.expectf(t, open_err == nil, "error on opening %q: %v", TEMPORARY_FILENAME, open_err) {
return
}
- defer testing.expect_value(t, os2.close(fd), nil)
+ defer testing.expect_value(t, os.close(fd), nil)
- stream := os2.to_stream(fd)
+ stream := os.to_stream(fd)
bytes_written, write_err := io.write(stream, buf[:])
if !testing.expectf(t, bytes_written == len(buf) && write_err == nil,
@@ -709,7 +671,7 @@ test_bufio_buffered_read_writer :: proc(t: ^testing.T) {
return
}
- // bufio.Read_Writer isn't capable of seeking, so we have to reset the os2
+ // bufio.Read_Writer isn't capable of seeking, so we have to reset the os
// stream back to the start here.
pos, seek_err := io.seek(stream, 0, .Start)
if !testing.expectf(t, pos == 0 && seek_err == nil,
diff --git a/tests/core/nbio/fs.odin b/tests/core/nbio/fs.odin
index 6e079f96e..1b10c03c9 100644
--- a/tests/core/nbio/fs.odin
+++ b/tests/core/nbio/fs.odin
@@ -1,9 +1,9 @@
package tests_nbio
-import "core:nbio"
-import "core:testing"
-import "core:time"
-import os "core:os/os2"
+import "core:nbio"
+import "core:testing"
+import "core:time"
+import "core:os"
@(test)
close_invalid_handle :: proc(t: ^testing.T) {
diff --git a/tests/core/nbio/nbio.odin b/tests/core/nbio/nbio.odin
index 2f454f55b..6c3fd0e8c 100644
--- a/tests/core/nbio/nbio.odin
+++ b/tests/core/nbio/nbio.odin
@@ -1,11 +1,11 @@
package tests_nbio
-import "core:log"
-import "core:nbio"
-import "core:testing"
-import "core:thread"
-import "core:time"
-import os "core:os/os2"
+import "core:log"
+import "core:nbio"
+import "core:testing"
+import "core:thread"
+import "core:time"
+import "core:os"
ev :: testing.expect_value
e :: testing.expect
diff --git a/tests/core/normal.odin b/tests/core/normal.odin
index d0889bf89..4708ed700 100644
--- a/tests/core/normal.odin
+++ b/tests/core/normal.odin
@@ -18,6 +18,7 @@ download_assets :: proc "contextless" () {
@(require) import "encoding/cbor"
@(require) import "encoding/hex"
@(require) import "encoding/hxa"
+@(require) import "encoding/ini"
@(require) import "encoding/json"
@(require) import "encoding/uuid"
@(require) import "encoding/varint"
@@ -36,8 +37,7 @@ download_assets :: proc "contextless" () {
@(require) import "net"
@(require) import "odin"
@(require) import "os"
-@(require) import "os/os2"
-@(require) import "path/filepath"
+@(require) import "os/old"
@(require) import "reflect"
@(require) import "runtime"
@(require) import "slice"
@@ -53,4 +53,4 @@ download_assets :: proc "contextless" () {
@(require) import "text/regex"
@(require) import "thread"
@(require) import "time"
-@(require) import "unicode"
+@(require) import "unicode"
\ No newline at end of file
diff --git a/tests/core/os/os2/dir.odin b/tests/core/os/dir.odin
similarity index 82%
rename from tests/core/os/os2/dir.odin
rename to tests/core/os/dir.odin
index 8ef333219..464abed98 100644
--- a/tests/core/os/os2/dir.odin
+++ b/tests/core/os/dir.odin
@@ -1,14 +1,14 @@
-package tests_core_os_os2
+package tests_core_os
-import os "core:os/os2"
-import "core:log"
-import "core:slice"
-import "core:testing"
-import "core:strings"
+import "core:os"
+import "core:log"
+import "core:slice"
+import "core:testing"
+import "core:strings"
@(test)
test_read_dir :: proc(t: ^testing.T) {
- path, err_join := os.join_path({#directory, "../dir"}, context.allocator)
+ path, err_join := os.join_path({#directory, "dir"}, context.allocator)
defer delete(path)
fis, err_read := os.read_all_directory_by_path(path, context.allocator)
@@ -17,7 +17,7 @@ test_read_dir :: proc(t: ^testing.T) {
slice.sort_by_key(fis, proc(fi: os.File_Info) -> string { return fi.name })
if err_read == .Unsupported {
- log.warn("os2 directory functionality is unsupported, skipping test")
+ log.warn("core:os directory functionality is unsupported, skipping test")
return
}
@@ -34,7 +34,7 @@ test_read_dir :: proc(t: ^testing.T) {
@(test)
test_walker :: proc(t: ^testing.T) {
- path, err := os.join_path({#directory, "../dir"}, context.allocator)
+ path, err := os.join_path({#directory, "dir"}, context.allocator)
defer delete(path)
testing.expect_value(t, err, nil)
@@ -46,7 +46,7 @@ test_walker :: proc(t: ^testing.T) {
@(test)
test_walker_file :: proc(t: ^testing.T) {
- path, err_join := os.join_path({#directory, "../dir"}, context.allocator)
+ path, err_join := os.join_path({#directory, "dir"}, context.allocator)
defer delete(path)
testing.expect_value(t, err_join, nil)
@@ -95,7 +95,7 @@ test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) {
}
if _, err := os.walker_error(w); err == .Unsupported {
- log.warn("os2 directory functionality is unsupported, skipping test")
+ log.warn("core:os directory functionality is unsupported, skipping test")
return
}
diff --git a/tests/core/os/os2/file.odin b/tests/core/os/file.odin
similarity index 90%
rename from tests/core/os/os2/file.odin
rename to tests/core/os/file.odin
index 0152a2008..aed57c26c 100644
--- a/tests/core/os/os2/file.odin
+++ b/tests/core/os/file.odin
@@ -1,7 +1,7 @@
-package tests_core_os_os2
+package tests_core_os
-import os "core:os/os2"
-import "core:testing"
+import "core:os"
+import "core:testing"
@(test)
test_clone :: proc(t: ^testing.T) {
diff --git a/tests/core/os/os.odin b/tests/core/os/old/os.odin
similarity index 97%
rename from tests/core/os/os.odin
rename to tests/core/os/old/os.odin
index 1510bad31..9925cf708 100644
--- a/tests/core/os/os.odin
+++ b/tests/core/os/old/os.odin
@@ -1,8 +1,8 @@
-package test_core_os
+package test_core_os_old
import "core:c/libc"
import win32 "core:sys/windows"
-import "core:os"
+import os "core:os/old"
import "core:slice"
import "core:testing"
import "core:log"
diff --git a/tests/core/os/os2/path.odin b/tests/core/os/path.odin
similarity index 69%
rename from tests/core/os/os2/path.odin
rename to tests/core/os/path.odin
index 7b1cb0146..cdfaed56f 100644
--- a/tests/core/os/os2/path.odin
+++ b/tests/core/os/path.odin
@@ -1,9 +1,11 @@
-package tests_core_os_os2
+package tests_core_os
-import os "core:os/os2"
-import "core:log"
-import "core:testing"
-import "core:strings"
+import "core:fmt"
+import "core:os"
+import "core:log"
+import "core:testing"
+import "core:slice"
+import "core:strings"
@(test)
test_executable :: proc(t: ^testing.T) {
@@ -334,6 +336,77 @@ test_join_filename :: proc(t: ^testing.T) {
}
}
+Glob_Test :: struct {
+ pattern: string,
+ matches: []string,
+ err: os.Error,
+}
+
+glob_tests := []Glob_Test{
+ {
+ pattern = ODIN_ROOT + "tests/core/os/*/*.txt",
+ matches = {
+ ODIN_ROOT + "tests/core/os/dir/b.txt",
+ },
+ err = {},
+ },
+ {
+ pattern = ODIN_ROOT + "tests/core/os/*.odin",
+ matches = {
+ ODIN_ROOT + "tests/core/os/dir.odin",
+ ODIN_ROOT + "tests/core/os/file.odin",
+ ODIN_ROOT + "tests/core/os/path.odin",
+ ODIN_ROOT + "tests/core/os/process.odin",
+ },
+ err = {},
+ },
+}
+
+@(test)
+test_glob :: proc(t: ^testing.T) {
+ compare_matches :: proc(t: ^testing.T, pattern: string, globbed, expected: []string) {
+ glob_fold := make([]string, len(globbed), context.temp_allocator)
+ expect_fold := make([]string, len(globbed), context.temp_allocator)
+
+ for glob, i in globbed {
+ // If `glob` returned a path in response to a pattern,
+ // then `match` should consider that path a match, too,
+ // irrespective of `/` versus `\` presence.
+ no_match_msg := fmt.tprintf("Expected os.match(%q, %q) to be `true`, got `false`", pattern, glob)
+ match, _ := os.match(pattern, glob)
+
+ f, _ := strings.replace_all(glob, `\`, `/`, context.temp_allocator)
+ glob_fold[i] = f
+ testing.expect(t, match, no_match_msg)
+ }
+
+ for exp, i in expected {
+ f, _ := strings.replace_all(exp, `\`, `/`, context.temp_allocator)
+ expect_fold[i] = f
+ }
+
+ slice.sort(glob_fold)
+ slice.sort(expect_fold)
+
+ not_equal_msg := fmt.tprintf("Expected os.glob(%q) to return %v, got %v", pattern, glob_fold, expect_fold)
+ testing.expect(t, slice.equal(glob_fold, expect_fold), not_equal_msg)
+ }
+
+ for glob in glob_tests {
+ globbed, err := os.glob(glob.pattern, context.allocator)
+ defer {
+ for file in globbed {
+ delete(file)
+ }
+ delete(globbed)
+ }
+ testing.expect_value(t, err, glob.err)
+ compare_matches(t, glob.pattern, globbed, glob.matches)
+ }
+}
+
+
+// TODO: merge this and `test_split_list`
@(test)
test_split_path_list :: proc(t: ^testing.T) {
Test_Case :: struct {
@@ -375,3 +448,115 @@ test_split_path_list :: proc(t: ^testing.T) {
}
}
}
+
+@(test)
+test_split_list :: proc(t: ^testing.T) {
+ when ODIN_OS == .Windows {
+ test_split_list_windows(t)
+ } else {
+ test_split_list_unix(t)
+ }
+}
+
+test_split_list_windows :: proc(t: ^testing.T) {
+ Datum :: struct {
+ i: int,
+ v: string,
+ e: [3]string,
+ }
+ @static data := []Datum{
+ { 0, "C:\\Odin;C:\\Visual Studio;\"C:\\Some Other\"",
+ [3]string{"C:\\Odin", "C:\\Visual Studio", "C:\\Some Other"} }, // Issue #1537
+ { 1, "a;;b", [3]string{"a", "", "b"} },
+ { 2, "a;b;", [3]string{"a", "b", ""} },
+ { 3, ";a;b", [3]string{"", "a", "b"} },
+ { 4, ";;", [3]string{"", "", ""} },
+ { 5, "\"a;b\"c;d;\"f\"", [3]string{"a;bc", "d", "f"} },
+ { 6, "\"a;b;c\";d\";e\";f", [3]string{"a;b;c", "d;e", "f"} },
+ }
+
+ for d, i in data {
+ assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
+ r, err := os.split_path_list(d.v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ defer delete_split(r)
+ testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e)))
+ if len(r) == len(d.e) {
+ for _, j in r {
+ testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", i, #procedure, d.v, r[j], j, d.e[j]))
+ }
+ }
+ }
+
+ {
+ v := ""
+ r, err := os.split_path_list(v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ defer delete_split(r)
+ testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
+ }
+ {
+ v := "a"
+ r, err := os.split_path_list(v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ defer delete_split(r)
+ testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
+ if len(r) == 1 {
+ testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0]))
+ }
+ }
+}
+
+test_split_list_unix :: proc(t: ^testing.T) {
+ Datum :: struct {
+ v: string,
+ e: [3]string,
+ }
+ @static data := []Datum{
+ { "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin",
+ [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537
+ { "a::b", [3]string{"a", "", "b"} },
+ { "a:b:", [3]string{"a", "b", ""} },
+ { ":a:b", [3]string{"", "a", "b"} },
+ { "::", [3]string{"", "", ""} },
+ { "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} },
+ { "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} },
+ }
+
+ for d in data {
+ r, err := os.split_path_list(d.v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ defer delete_split(r)
+ testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e))
+ if len(r) == len(d.e) {
+ for _, j in r {
+ testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j])
+ }
+ }
+ }
+
+ {
+ v := ""
+ r, err := os.split_path_list(v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r)
+ }
+ {
+ v := "a"
+ r, err := os.split_path_list(v, context.allocator)
+ testing.expectf(t, err == nil, "Expected err to be nil, got %v", err)
+ defer delete_split(r)
+ testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r))
+ if len(r) == 1 {
+ testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0])
+ }
+ }
+}
+
+@(private)
+delete_split :: proc(s: []string) {
+ for part in s {
+ delete(part)
+ }
+ delete(s)
+}
\ No newline at end of file
diff --git a/tests/core/os/os2/process.odin b/tests/core/os/process.odin
similarity index 84%
rename from tests/core/os/os2/process.odin
rename to tests/core/os/process.odin
index c530b4c79..adb65e95f 100644
--- a/tests/core/os/os2/process.odin
+++ b/tests/core/os/process.odin
@@ -1,9 +1,9 @@
#+build !windows
-package tests_core_os_os2
+package tests_core_os
-import os "core:os/os2"
-import "core:log"
-import "core:testing"
+import "core:os"
+import "core:log"
+import "core:testing"
@(test)
test_process_exec :: proc(t: ^testing.T) {
diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin
index f0137f69b..a0de7e831 100644
--- a/tests/core/path/filepath/test_core_filepath.odin
+++ b/tests/core/path/filepath/test_core_filepath.odin
@@ -33,7 +33,7 @@ test_split_list_windows :: proc(t: ^testing.T) {
for d, i in data {
assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
- r := filepath.split_list(d.v)
+ r, _ := filepath.split_list(d.v, context.allocator)
defer delete_split(r)
testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e)))
if len(r) == len(d.e) {
@@ -45,13 +45,13 @@ test_split_list_windows :: proc(t: ^testing.T) {
{
v := ""
- r := filepath.split_list(v)
+ r, _ := filepath.split_list(v, context.allocator)
defer delete_split(r)
testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
}
{
v := "a"
- r := filepath.split_list(v)
+ r, _ := filepath.split_list(v, context.allocator)
defer delete_split(r)
testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
if len(r) == 1 {
@@ -77,7 +77,7 @@ test_split_list_unix :: proc(t: ^testing.T) {
}
for d in data {
- r := filepath.split_list(d.v)
+ r, _ := filepath.split_list(d.v, context.allocator)
defer delete_split(r)
testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e))
if len(r) == len(d.e) {
@@ -89,12 +89,12 @@ test_split_list_unix :: proc(t: ^testing.T) {
{
v := ""
- r := filepath.split_list(v)
+ r, _ := filepath.split_list(v, context.allocator)
testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r)
}
{
v := "a"
- r := filepath.split_list(v)
+ r, _ := filepath.split_list(v, context.allocator)
defer delete_split(r)
testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r))
if len(r) == 1 {
diff --git a/tests/core/sys/kqueue/structs.odin b/tests/core/sys/kqueue/structs.odin
index edf1fdd1e..15ec3f841 100644
--- a/tests/core/sys/kqueue/structs.odin
+++ b/tests/core/sys/kqueue/structs.odin
@@ -1,9 +1,9 @@
#+build darwin, freebsd, openbsd, netbsd
package tests_core_sys_kqueue
-import "core:strings"
-import "core:testing"
-import os "core:os/os2"
+import "core:strings"
+import "core:testing"
+import "core:os"
@(test)
structs :: proc(t: ^testing.T) {
diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin
index cd2b19fb8..6e5e47696 100644
--- a/tests/core/time/test_core_time.odin
+++ b/tests/core/time/test_core_time.odin
@@ -2,6 +2,7 @@ package test_core_time
import "core:testing"
import "core:time"
+@(require) import "core:log"
import dt "core:time/datetime"
import tz "core:time/timezone"
@@ -364,9 +365,10 @@ test_convert_timezone_roundtrip :: proc(t: ^testing.T) {
std_dt, _ := dt.components_to_datetime(2024, 11, 4, 23, 47, 0)
local_tz, local_load_ok := tz.region_load("local")
- testing.expectf(t, local_load_ok, "Failed to load local timezone")
defer tz.region_destroy(local_tz)
+ testing.expectf(t, local_load_ok, "Failed to load local timezone")
+
edm_tz, edm_load_ok := tz.region_load("America/Edmonton")
testing.expectf(t, edm_load_ok, "Failed to load America/Edmonton timezone")
defer tz.region_destroy(edm_tz)
diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin
index 7b125d4e4..be59d9b4d 100644
--- a/tests/documentation/documentation_tester.odin
+++ b/tests/documentation/documentation_tester.odin
@@ -1,12 +1,11 @@
package documentation_tester
-import "core:os"
-import "core:io"
-import "core:fmt"
-import "core:strings"
-import "core:odin/ast"
-import "core:odin/parser"
-import "core:c/libc"
+import "core:os"
+import "core:fmt"
+import "core:strings"
+import "core:odin/ast"
+import "core:odin/parser"
+import "core:c/libc"
import doc "core:odin/doc-format"
Example_Test :: struct {
@@ -63,10 +62,11 @@ main :: proc() {
errorf("expected path to odin executable")
}
g_path_to_odin = os.args[1]
- data, ok := os.read_entire_file("all.odin-doc")
- if !ok {
+ data, data_err := os.read_entire_file("all.odin-doc", context.allocator)
+ if data_err != nil {
errorf("unable to read file: all.odin-doc")
}
+ defer delete(data)
err: doc.Reader_Error
g_header, err = doc.read_from_bytes(data)
switch err {
@@ -257,8 +257,8 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s
write_test_suite :: proc(example_tests: []Example_Test) {
TEST_SUITE_DIRECTORY :: "verify"
- os.remove_directory(TEST_SUITE_DIRECTORY)
- os.make_directory(TEST_SUITE_DIRECTORY)
+ os.remove_all(TEST_SUITE_DIRECTORY)
+ os.mkdir(TEST_SUITE_DIRECTORY)
example_build := strings.builder_make()
test_runner := strings.builder_make()
@@ -276,9 +276,11 @@ import "core:sync"
import "base:intrinsics"
@(private="file")
-_read_pipe: os.Handle
+_read_pipe: ^os.File
@(private="file")
-_write_pipe: os.Handle
+_write_pipe: ^os.File
+@(private="file")
+_old_stdout: ^os.File
@(private="file")
_pipe_reader_semaphore: sync.Sema
@(private="file")
@@ -286,20 +288,20 @@ _out_data: string
@(private="file")
_out_buffer: [mem.Megabyte]byte
@(private="file")
-_bad_test_found: bool
+_bad_count: int
+@(private="file")
+_good_count: int
@(private="file")
_spawn_pipe_reader :: proc() {
thread.run(proc() {
- stream := os.stream_from_handle(_read_pipe)
- reader := io.to_reader(stream)
sync.post(&_pipe_reader_semaphore) // notify thread is ready
for {
n_read := 0
read_to_null_byte := 0
finished_reading := false
for ! finished_reading {
- just_read, err := io.read(reader, _out_buffer[n_read:], &n_read); if err != .None {
+ just_read, err := io.read(os.to_stream(_read_pipe), _out_buffer[n_read:], &n_read); if err != .None {
panic("We got an IO error!")
}
for b in _out_buffer[n_read - just_read: n_read] {
@@ -328,11 +330,14 @@ _check :: proc(test_name: string, expected: string) {
if expected != output {
fmt.eprintf("Test %q got unexpected output:\n%q\n", test_name, output)
fmt.eprintf("Expected:\n%q\n", expected)
- _bad_test_found = true
+ _bad_count += 1
+ } else {
+ _good_count += 1
}
}
main :: proc() {
+ _old_stdout = os.stdout
_read_pipe, _write_pipe, _ = os.pipe()
os.stdout = _write_pipe
_spawn_pipe_reader()
@@ -445,24 +450,19 @@ main :: proc() {
continue
}
defer os.close(test_file_handle)
- stream := os.stream_from_handle(test_file_handle)
- writer, ok := io.to_writer(stream); if ! ok {
- fmt.eprintf("We could not make the writer for the path %q\n", save_path)
- g_bad_doc = true
- continue
- }
- fmt.wprintf(writer, "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:])
+ fmt.wprintf(os.to_stream(test_file_handle), "%v%v_%v", code_string[:index_of_proc_name], test.package_name, code_string[index_of_proc_name:])
fmt.println("Done")
}
strings.write_string(&test_runner,
`
- if _bad_test_found {
+ fmt.wprintfln(os.to_stream(_old_stdout), "Passes: %v. Fails: %v", _good_count, _bad_count)
+ if _bad_count > 0 {
fmt.eprintln("One or more tests failed")
os.exit(1)
}
}`)
- os.write_entire_file("verify/main.odin", transmute([]byte)strings.to_string(test_runner))
+ _ = os.write_entire_file("verify/main.odin", transmute([]byte)strings.to_string(test_runner))
}
run_test_suite :: proc() -> bool {
diff --git a/vendor/OpenGL/helpers.odin b/vendor/OpenGL/helpers.odin
index 84e3eae81..9c9c74d09 100644
--- a/vendor/OpenGL/helpers.odin
+++ b/vendor/OpenGL/helpers.odin
@@ -3,10 +3,11 @@ package vendor_gl
// Helper for loading shaders into a program
-import "core:os"
-import "core:fmt"
-import "core:strings"
-import "base:runtime"
+import "core:os"
+import "core:fmt"
+import "core:strings"
+@(require) import "core:time"
+import "base:runtime"
_ :: fmt
_ :: runtime
@@ -150,7 +151,10 @@ create_and_link_program :: proc(shader_ids: []u32, binary_retrievable := false)
}
load_compute_file :: proc(filename: string, binary_retrievable := false) -> (program_id: u32, ok: bool) {
- cs_data := os.read_entire_file(filename) or_return
+ cs_data, cs_data_err := os.read_entire_file(filename, context.allocator)
+ if cs_data_err != nil {
+ return 0, false
+ }
defer delete(cs_data)
// Create the shaders
@@ -165,10 +169,16 @@ load_compute_source :: proc(cs_data: string, binary_retrievable := false) -> (pr
}
load_shaders_file :: proc(vs_filename, fs_filename: string, binary_retrievable := false) -> (program_id: u32, ok: bool) {
- vs_data := os.read_entire_file(vs_filename) or_return
+ vs_data, vs_data_err := os.read_entire_file(vs_filename, context.allocator)
+ if vs_data_err != nil {
+ return 0, false
+ }
defer delete(vs_data)
- fs_data := os.read_entire_file(fs_filename) or_return
+ fs_data, fs_data_err := os.read_entire_file(fs_filename, context.allocator)
+ if fs_data_err != nil {
+ return 0, false
+ }
defer delete(fs_data)
return load_shaders_source(string(vs_data), string(fs_data), binary_retrievable)
@@ -192,14 +202,14 @@ when ODIN_OS == .Windows {
update_shader_if_changed :: proc(
vertex_name, fragment_name: string,
program: u32,
- last_vertex_time, last_fragment_time: os.File_Time,
+ last_vertex_time, last_fragment_time: time.Time,
) -> (
old_program: u32,
- current_vertex_time, current_fragment_time: os.File_Time,
+ current_vertex_time, current_fragment_time: time.Time,
updated: bool,
) {
- current_vertex_time, _ = os.last_write_time_by_name(vertex_name)
- current_fragment_time, _ = os.last_write_time_by_name(fragment_name)
+ current_vertex_time, _ = os.modification_time_by_path(vertex_name)
+ current_fragment_time, _ = os.modification_time_by_path(fragment_name)
old_program = program
if current_vertex_time != last_vertex_time || current_fragment_time != last_fragment_time {
@@ -220,13 +230,13 @@ when ODIN_OS == .Windows {
update_shader_if_changed_compute :: proc(
compute_name: string,
program: u32,
- last_compute_time: os.File_Time,
+ last_compute_time: time.Time,
) -> (
old_program: u32,
- current_compute_time: os.File_Time,
+ current_compute_time: time.Time,
updated: bool,
) {
- current_compute_time, _ = os.last_write_time_by_name(compute_name)
+ current_compute_time, _ = os.modification_time_by_path(compute_name)
old_program = program
if current_compute_time != last_compute_time {
diff --git a/vendor/fontstash/fontstash_os.odin b/vendor/fontstash/fontstash_os.odin
index e510a4834..d04df044c 100644
--- a/vendor/fontstash/fontstash_os.odin
+++ b/vendor/fontstash/fontstash_os.odin
@@ -12,12 +12,11 @@ AddFontPath :: proc(
path: string,
fontIndex: int = 0,
) -> int {
- data, ok := os.read_entire_file(path)
+ data, data_err := os.read_entire_file(path, context.allocator)
- if !ok {
+ if data_err != nil {
log.panicf("FONT: failed to read font at %s", path)
}
return AddFontMem(ctx, name, data, true, fontIndex)
-}
-
+}
\ No newline at end of file
diff --git a/vendor/libc-shim/stdio.odin b/vendor/libc-shim/stdio.odin
index e269b8986..b47b3f166 100644
--- a/vendor/libc-shim/stdio.odin
+++ b/vendor/libc-shim/stdio.odin
@@ -4,93 +4,60 @@ package odin_libc
import "base:runtime"
import "core:c"
-import "core:io"
-import "core:os"
import "core:strconv"
import stb "vendor:stb/sprintf"
-FILE :: uintptr
+FILE :: rawptr
EOF :: -1
@(require, linkage="strong", link_name="fopen")
fopen :: proc "c" (path: cstring, mode: cstring) -> FILE {
context = g_ctx
- unimplemented("vendor/libc-shim: fopen")
+ return _fopen(path, mode)
}
@(require, linkage="strong", link_name="fseek")
fseek :: proc "c" (file: FILE, offset: c.long, whence: i32) -> i32 {
context = g_ctx
- handle := os.Handle(file-1)
- _, err := os.seek(handle, i64(offset), int(whence))
- if err != nil {
- return -1
- }
- return 0
+ return _fseek(file, offset, whence)
}
@(require, linkage="strong", link_name="ftell")
ftell :: proc "c" (file: FILE) -> c.long {
context = g_ctx
- handle := os.Handle(file-1)
- off, err := os.seek(handle, 0, os.SEEK_CUR)
- if err != nil {
- return -1
- }
- return c.long(off)
+ return _ftell(file)
}
@(require, linkage="strong", link_name="fclose")
fclose :: proc "c" (file: FILE) -> i32 {
context = g_ctx
- handle := os.Handle(file-1)
- if os.close(handle) != nil {
- return -1
- }
- return 0
+ return _fclose(file)
}
@(require, linkage="strong", link_name="fread")
fread :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
context = g_ctx
- handle := os.Handle(file-1)
- n, _ := os.read(handle, buffer[:min(size, count)])
- return uint(max(0, n))
+ return _fread(buffer, size, count, file)
}
@(require, linkage="strong", link_name="fwrite")
fwrite :: proc "c" (buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
context = g_ctx
- handle := os.Handle(file-1)
- n, _ := os.write(handle, buffer[:min(size, count)])
- return uint(max(0, n))
+ return _fwrite(buffer, size, count, file)
}
@(require, linkage="strong", link_name="putchar")
putchar :: proc "c" (char: c.int) -> c.int {
context = g_ctx
-
- n, err := os.write_byte(os.stdout, byte(char))
- if n == 0 || err != nil {
- return EOF
- }
- return char
+ return _putchar(char)
}
@(require, linkage="strong", link_name="getchar")
getchar :: proc "c" () -> c.int {
- when #defined(os.stdin) {
- ret: [1]byte
- n, err := os.read(os.stdin, ret[:])
- if n == 0 || err != nil {
- return EOF
- }
- return c.int(ret[0])
- } else {
- return EOF
- }
+ context = g_ctx
+ return _getchar()
}
@(require, linkage="strong", link_name="vsnprintf")
@@ -109,8 +76,6 @@ vsprintf :: proc "c" (buf: [^]byte, fmt: cstring, args: ^c.va_list) -> i32 {
vfprintf :: proc "c" (file: FILE, fmt: cstring, args: ^c.va_list) -> i32 {
context = g_ctx
- handle := os.Handle(file-1)
-
MAX_STACK :: 4096
buf: []byte
@@ -133,12 +98,15 @@ vfprintf :: proc "c" (file: FILE, fmt: cstring, args: ^c.va_list) -> i32 {
delete(buf)
}
- _, err := io.write_full(os.stream_from_handle(handle), buf)
- if err != nil {
- return -1
+ written: i32
+ for len(buf) > 0 {
+ n := _fwrite(raw_data(buf), size_of(byte), len(buf), file)
+ if n == 0 { break }
+ buf = buf[n:]
+ written += i32(n)
}
- return i32(len(buf))
+ return written
}
/*
diff --git a/vendor/libc-shim/stdio_js.odin b/vendor/libc-shim/stdio_js.odin
new file mode 100644
index 000000000..2382ed449
--- /dev/null
+++ b/vendor/libc-shim/stdio_js.odin
@@ -0,0 +1,60 @@
+#+private
+package odin_libc
+
+import "core:c"
+
+foreign import "odin_env"
+
+_fopen :: proc(path, mode: cstring) -> FILE {
+ unimplemented("vendor/libc: fopen in JS")
+}
+
+_fseek :: proc(file: FILE, offset: c.long, whence: i32) -> i32 {
+ unimplemented("vendor/libc: fseek in JS")
+}
+
+_ftell :: proc(file: FILE) -> c.long {
+ unimplemented("vendor/libc: ftell in JS")
+}
+
+_fclose :: proc(file: FILE) -> i32 {
+ unimplemented("vendor/libc: fclose in JS")
+}
+
+_fread :: proc(buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
+ unimplemented("vendor/libc: fread in JS")
+}
+
+_fwrite :: proc(buffer: [^]byte, size: uint, count: uint, file: FILE) -> uint {
+ fd, ok := __fd(file)
+ if !ok {
+ return 0
+ }
+
+ __write(fd, buffer[:size*count])
+ return count
+}
+
+_putchar :: proc(char: c.int) -> c.int {
+ __write(1, {byte(char)})
+ return char
+}
+
+_getchar :: proc() -> c.int {
+ return EOF
+}
+
+@(private="file")
+foreign odin_env {
+ @(link_name="write")
+ __write :: proc "contextless" (fd: u32, p: []byte) ---
+}
+
+@(private="file")
+__fd :: proc(file: FILE) -> (u32, bool) {
+ switch (uint(uintptr(file))) {
+ case 2: return 1, true // stdout
+ case 3: return 2, true // stderr
+ case: return 0, false
+ }
+}
diff --git a/vendor/libc-shim/stdio_os.odin b/vendor/libc-shim/stdio_os.odin
new file mode 100644
index 000000000..f6d30a227
--- /dev/null
+++ b/vendor/libc-shim/stdio_os.odin
@@ -0,0 +1,104 @@
+#+build !freestanding
+#+build !js
+package odin_libc
+
+import "core:io"
+import "core:c"
+import "core:os"
+
+_fopen :: proc(path, _mode: cstring) -> FILE {
+ flags: os.File_Flags
+
+ mode := string(_mode)
+ if len(mode) > 1 {
+ switch mode[0] {
+ case 'r':
+ flags += {.Read}
+ case 'w':
+ flags += {.Write, .Create, .Trunc}
+ case 'a':
+ flags += {.Write, .Create, .Append}
+ case:
+ return nil
+ }
+
+ if len(mode) > 1 && mode[1] == '+' {
+ flags += {.Write, .Read}
+ } else if len(mode) > 2 && mode[1] == 'b' && mode[2] == '+' {
+ flags += {.Write, .Read}
+ }
+ }
+
+ file, err := os.open(string(path), flags, os.Permissions_Read_Write_All)
+ if err != nil {
+ return nil
+ }
+
+ return FILE(file)
+}
+
+_fseek :: proc(_file: FILE, offset: c.long, whence: i32) -> i32 {
+ file := __file(_file)
+ if _, err := os.seek(file, i64(offset), io.Seek_From(whence)); err != nil {
+ return -1
+ }
+
+ return 0
+}
+
+_ftell :: proc(_file: FILE) -> c.long {
+ file := __file(_file)
+ pos, err := os.seek(file, 0, .Current)
+ if err != nil {
+ return -1
+ }
+
+ return c.long(pos)
+}
+
+_fclose :: proc(_file: FILE) -> i32 {
+ file := __file(_file)
+ if err := os.close(file); err != nil {
+ return EOF
+ }
+
+ return 0
+}
+
+_fread :: proc(buffer: [^]byte, size: uint, count: uint, _file: FILE) -> uint {
+ file := __file(_file)
+ n, _ := os.read(file, buffer[:size*count])
+ return uint(max(0, n)) / size
+}
+
+_fwrite :: proc(buffer: [^]byte, size: uint, count: uint, _file: FILE) -> uint {
+ file := __file(_file)
+ n, _ := os.write(file, buffer[:size*count])
+ return uint(max(0, n)) / size
+}
+
+_putchar :: proc(char: c.int) -> c.int {
+ n, err := os.write_byte(os.stdout, byte(char))
+ if n == 0 || err != nil {
+ return EOF
+ }
+ return char
+}
+
+_getchar :: proc() -> c.int {
+ ret: [1]byte
+ n, err := os.read(os.stdin, ret[:])
+ if n == 0 || err != nil {
+ return EOF
+ }
+ return c.int(ret[0])
+}
+
+@(private="file")
+__file :: proc(file: FILE) -> ^os.File {
+ switch (uint(uintptr(file))) {
+ case 2: return os.stdout
+ case 3: return os.stderr
+ case: return (^os.File)(file)
+ }
+}
diff --git a/vendor/libc-shim/stdlib.odin b/vendor/libc-shim/stdlib.odin
index cffc66ed2..5dd4c53c1 100644
--- a/vendor/libc-shim/stdlib.odin
+++ b/vendor/libc-shim/stdlib.odin
@@ -5,7 +5,6 @@ import "base:intrinsics"
import "base:runtime"
import "core:c"
-import "core:os"
import "core:slice"
import "core:sort"
import "core:strconv"
@@ -166,7 +165,7 @@ atexit :: proc "c" (function: proc "c" ()) -> i32 {
@(require, linkage="strong", link_name="exit")
exit :: proc "c" (exit_code: c.int) -> ! {
finish_atexit()
- os.exit(int(exit_code))
+ runtime.exit(int(exit_code))
}
@(private, fini)