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