Merge pull request #6245 from odin-lang/new_os

`core:os/os2` -> `core:os` integration
This commit is contained in:
gingerBill
2026-02-11 17:53:16 +00:00
committed by GitHub
211 changed files with 4332 additions and 3849 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 := "<input>", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
Reader :: struct {
filename: string,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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..<len(file) {
#no_bounds_check switch file[i] {
case 'r': wants_read = true
case 'w': wants_write = true
case 'c': mode |= os.O_CREATE
case 'a': mode |= os.O_APPEND
case 't': mode |= os.O_TRUNC
case 'r': flags |= {.Read}
case 'w': flags |= {.Write}
case 'c': flags |= {.Create}
case 'a': flags |= {.Append}
case 't': flags |= {.Trunc}
}
}
}
// Sane default.
// owner/group/other: r--r--r--
perms: int = 0o444
octal_perms: int = 0o444
if wants_read && wants_write {
mode |= os.O_RDWR
perms |= 0o200
} else if wants_write {
mode |= os.O_WRONLY
perms |= 0o200
if flags >= {.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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

136
core/image/png/example.odin Normal file
View File

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

View File

@@ -1,4 +1,6 @@
#+feature using-stmt
// Reader for `PNG` images.
// The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]].
package png
/*

View File

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

View File

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

View File

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

View File

@@ -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, "] ")
}
}

View File

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

View File

@@ -0,0 +1,79 @@
#+build !freestanding
#+build !js
package math_big
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,6 @@ package net
@(require)
import "base:runtime"
import "core:bufio"
import "core:io"
import "core:math/rand"

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

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

24
core/os/dir_js.odin Normal file
View File

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

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "core:sys/linux"

View File

@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
package os2
package os
import "core:sys/posix"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "core:sys/darwin"

View File

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

View File

@@ -1,7 +1,6 @@
#+private
package os2
package os
import "base:runtime"
import "core:slice"
import "base:intrinsics"
import "core:sys/wasm/wasi"

View File

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

6
core/os/doc.odin Normal file
View File

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

View File

@@ -1,4 +1,4 @@
package os2
package os
import "base:runtime"
import "core:strings"

42
core/os/env_js.odin Normal file
View File

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

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"
import "base:intrinsics"

View File

@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
package os2
package os
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -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..<len(env) {
if env[j] == '=' {
@@ -138,3 +95,48 @@ clear_env :: proc() {
}
}
}
_environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) {
envs := win32.GetEnvironmentStringsW()
if envs == nil {
return
}
defer win32.FreeEnvironmentStringsW(envs)
n := 0
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
n += 1
from = i + 1
}
}
r := make([dynamic]string, 0, n, allocator) or_return
defer if err != nil {
for e in r {
delete(e, allocator)
}
delete(r)
}
for from, i, p := 0, 0, envs; true; i += 1 {
c := ([^]u16)(p)[i]
if c == 0 {
if i <= from {
break
}
w := ([^]u16)(p)[from:i]
s := win32_utf16_to_utf8(w, allocator) or_return
append(&r, s)
from = i + 1
}
}
environ = r[:]
return
}

View File

@@ -1,13 +1,12 @@
package os
import "base:intrinsics"
import "base:runtime"
import "core:io"
import "base:runtime"
Platform_Error :: _Platform_Error
#assert(size_of(Platform_Error) <= 4)
#assert(intrinsics.type_has_nil(Platform_Error))
/*
General errors that are common within this package which cannot
be categorized by `io.Error` nor `runtime.Allocator_Error`.
*/
General_Error :: enum u32 {
None,
@@ -22,39 +21,43 @@ General_Error :: enum u32 {
Invalid_Dir,
Invalid_Path,
Invalid_Callback,
Invalid_Command,
Pattern_Has_Separator,
Pattern_Syntax_Error, // Indicates an error in `glob` or `match` pattern.
File_Is_Pipe,
Not_Dir,
// Environment variable not found.
No_HOME_Variable,
Env_Var_Not_Found,
}
// A platform specific error
Platform_Error :: _Platform_Error
Errno :: Error // alias for legacy use
/*
`Error` is a union of different classes of errors that could be returned from procedures in this package.
*/
Error :: union #shared_nil {
General_Error,
io.Error,
runtime.Allocator_Error,
Platform_Error,
}
#assert(size_of(Error) == 8)
#assert(size_of(Error) == size_of(u64))
ERROR_NONE :: Error{}
ERROR_EOF :: io.Error.EOF
// Attempts to convert an `Error` into a platform specific error as an integer. `ok` is false if not possible
@(require_results)
is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {}
return i32(v), i32(v) != 0
}
// Attempts to return the error `ferr` as a string without any allocation
@(require_results)
error_string :: proc "contextless" (ferr: Error) -> string {
error_string :: proc(ferr: Error) -> string {
if ferr == nil {
return ""
}
@@ -62,18 +65,19 @@ error_string :: proc "contextless" (ferr: Error) -> string {
case General_Error:
switch e {
case .None: return ""
case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist"
case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe"
case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Pattern_Has_Separator: return "pattern has separator"
case .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist"
case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe"
case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Invalid_Command: return "invalid command"
case .Pattern_Has_Separator: return "pattern has separator"
case .Pattern_Syntax_Error: return "glob pattern syntax error"
case .No_HOME_Variable: return "no $HOME variable"
case .Env_Var_Not_Found: return "environment variable not found"
}
case io.Error:
switch e {
@@ -106,210 +110,35 @@ error_string :: proc "contextless" (ferr: Error) -> string {
case .Mode_Not_Implemented: return "allocator mode not implemented"
}
case Platform_Error:
return _error_string(e)
return _error_string(i32(e))
}
return "unknown error"
}
print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
/*
`print_error` is a utility procedure which will print an error `ferr` to a specified file `f`.
*/
print_error :: proc(f: ^File, ferr: Error, msg: string) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr)
// msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1
buf_ := intrinsics.alloca(length, 1)
buf := buf_[:length]
buf := make([]u8, length, temp_allocator)
copy(buf, msg)
buf[len(msg)] = ':'
buf[len(msg) + 1] = ' '
copy(buf[len(msg) + 2:], err_str)
buf[length - 1] = '\n'
return write(f, buf)
write(f, buf)
}
@(require_results, private)
_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) {
if e == nil {
return ""
}
when ODIN_OS == .Darwin {
if s := string(_darwin_string_error(i32(e))); s != "" {
return s
}
}
when ODIN_OS != .Linux {
@(require_results)
binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check {
n := len(array)
left, right := 0, n
for left < right {
mid := int(uint(left+right) >> 1)
if array[mid] < key {
left = mid+1
} else {
// equal or greater
right = mid
}
}
return left, left < n && array[left] == key
}
err := runtime.Type_Info_Enum_Value(e)
ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum)
if idx, ok := binary_search(ti.values, err); ok {
return ti.names[idx]
}
} else {
@(rodata, static)
pe_strings := [Platform_Error]string{
.NONE = "",
.EPERM = "Operation not permitted",
.ENOENT = "No such file or directory",
.ESRCH = "No such process",
.EINTR = "Interrupted system call",
.EIO = "Input/output error",
.ENXIO = "No such device or address",
.E2BIG = "Argument list too long",
.ENOEXEC = "Exec format error",
.EBADF = "Bad file descriptor",
.ECHILD = "No child processes",
.EAGAIN = "Resource temporarily unavailable",
.ENOMEM = "Cannot allocate memory",
.EACCES = "Permission denied",
.EFAULT = "Bad address",
.ENOTBLK = "Block device required",
.EBUSY = "Device or resource busy",
.EEXIST = "File exists",
.EXDEV = "Invalid cross-device link",
.ENODEV = "No such device",
.ENOTDIR = "Not a directory",
.EISDIR = "Is a directory",
.EINVAL = "Invalid argument",
.ENFILE = "Too many open files in system",
.EMFILE = "Too many open files",
.ENOTTY = "Inappropriate ioctl for device",
.ETXTBSY = "Text file busy",
.EFBIG = "File too large",
.ENOSPC = "No space left on device",
.ESPIPE = "Illegal seek",
.EROFS = "Read-only file system",
.EMLINK = "Too many links",
.EPIPE = "Broken pipe",
.EDOM = "Numerical argument out of domain",
.ERANGE = "Numerical result out of range",
.EDEADLK = "Resource deadlock avoided",
.ENAMETOOLONG = "File name too long",
.ENOLCK = "No locks available",
.ENOSYS = "Function not implemented",
.ENOTEMPTY = "Directory not empty",
.ELOOP = "Too many levels of symbolic links",
.EUNKNOWN_41 = "Unknown Error (41)",
.ENOMSG = "No message of desired type",
.EIDRM = "Identifier removed",
.ECHRNG = "Channel number out of range",
.EL2NSYNC = "Level 2 not synchronized",
.EL3HLT = "Level 3 halted",
.EL3RST = "Level 3 reset",
.ELNRNG = "Link number out of range",
.EUNATCH = "Protocol driver not attached",
.ENOCSI = "No CSI structure available",
.EL2HLT = "Level 2 halted",
.EBADE = "Invalid exchange",
.EBADR = "Invalid request descriptor",
.EXFULL = "Exchange full",
.ENOANO = "No anode",
.EBADRQC = "Invalid request code",
.EBADSLT = "Invalid slot",
.EUNKNOWN_58 = "Unknown Error (58)",
.EBFONT = "Bad font file format",
.ENOSTR = "Device not a stream",
.ENODATA = "No data available",
.ETIME = "Timer expired",
.ENOSR = "Out of streams resources",
.ENONET = "Machine is not on the network",
.ENOPKG = "Package not installed",
.EREMOTE = "Object is remote",
.ENOLINK = "Link has been severed",
.EADV = "Advertise error",
.ESRMNT = "Srmount error",
.ECOMM = "Communication error on send",
.EPROTO = "Protocol error",
.EMULTIHOP = "Multihop attempted",
.EDOTDOT = "RFS specific error",
.EBADMSG = "Bad message",
.EOVERFLOW = "Value too large for defined data type",
.ENOTUNIQ = "Name not unique on network",
.EBADFD = "File descriptor in bad state",
.EREMCHG = "Remote address changed",
.ELIBACC = "Can not access a needed shared library",
.ELIBBAD = "Accessing a corrupted shared library",
.ELIBSCN = ".lib section in a.out corrupted",
.ELIBMAX = "Attempting to link in too many shared libraries",
.ELIBEXEC = "Cannot exec a shared library directly",
.EILSEQ = "Invalid or incomplete multibyte or wide character",
.ERESTART = "Interrupted system call should be restarted",
.ESTRPIPE = "Streams pipe error",
.EUSERS = "Too many users",
.ENOTSOCK = "Socket operation on non-socket",
.EDESTADDRREQ = "Destination address required",
.EMSGSIZE = "Message too long",
.EPROTOTYPE = "Protocol wrong type for socket",
.ENOPROTOOPT = "Protocol not available",
.EPROTONOSUPPORT = "Protocol not supported",
.ESOCKTNOSUPPORT = "Socket type not supported",
.EOPNOTSUPP = "Operation not supported",
.EPFNOSUPPORT = "Protocol family not supported",
.EAFNOSUPPORT = "Address family not supported by protocol",
.EADDRINUSE = "Address already in use",
.EADDRNOTAVAIL = "Cannot assign requested address",
.ENETDOWN = "Network is down",
.ENETUNREACH = "Network is unreachable",
.ENETRESET = "Network dropped connection on reset",
.ECONNABORTED = "Software caused connection abort",
.ECONNRESET = "Connection reset by peer",
.ENOBUFS = "No buffer space available",
.EISCONN = "Transport endpoint is already connected",
.ENOTCONN = "Transport endpoint is not connected",
.ESHUTDOWN = "Cannot send after transport endpoint shutdown",
.ETOOMANYREFS = "Too many references: cannot splice",
.ETIMEDOUT = "Connection timed out",
.ECONNREFUSED = "Connection refused",
.EHOSTDOWN = "Host is down",
.EHOSTUNREACH = "No route to host",
.EALREADY = "Operation already in progress",
.EINPROGRESS = "Operation now in progress",
.ESTALE = "Stale file handle",
.EUCLEAN = "Structure needs cleaning",
.ENOTNAM = "Not a XENIX named type file",
.ENAVAIL = "No XENIX semaphores available",
.EISNAM = "Is a named type file",
.EREMOTEIO = "Remote I/O error",
.EDQUOT = "Disk quota exceeded",
.ENOMEDIUM = "No medium found",
.EMEDIUMTYPE = "Wrong medium type",
.ECANCELED = "Operation canceled",
.ENOKEY = "Required key not available",
.EKEYEXPIRED = "Key has expired",
.EKEYREVOKED = "Key has been revoked",
.EKEYREJECTED = "Key was rejected by service",
.EOWNERDEAD = "Owner died",
.ENOTRECOVERABLE = "State not recoverable",
.ERFKILL = "Operation not possible due to RF-kill",
.EHWPOISON = "Memory page has hardware error",
}
if Platform_Error.NONE <= e && e <= max(Platform_Error) {
return pe_strings[e]
}
}
return "<unknown platform error>"
}
@(private, require_results)
// Attempts to convert an `Error` `ferr` into an `io.Error`
@(private)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None

13
core/os/errors_js.odin Normal file
View File

@@ -0,0 +1,13 @@
#+build js wasm32, js wasm64p32
#+private
package os
_Platform_Error :: enum i32 {}
_error_string :: proc(errno: i32) -> string {
return "<unknown platform error>"
}
_get_platform_error :: proc(errno: _Platform_Error) -> Error {
return Platform_Error(errno)
}

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "core:sys/linux"

View File

@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
package os2
package os
import "core:sys/posix"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"
import "core:slice"

View File

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

110
core/os/file_js.odin Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -1,6 +1,6 @@
#+private
#+build openbsd
package os2
package os
import "base:runtime"

View File

@@ -1,4 +1,4 @@
package os2
package os
import "base:intrinsics"
import "base:runtime"

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package os2
package os
import "base:runtime"

7
core/os/heap_js.odin Normal file
View File

@@ -0,0 +1,7 @@
#+build js wasm32, js wasm64p32
#+private
package os
import "base:runtime"
_heap_allocator_proc :: runtime.wasm_allocator_proc

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -1,6 +1,6 @@
#+private
#+build darwin, netbsd, freebsd, openbsd
package os2
package os
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "core:mem"
import win32 "core:sys/windows"

View File

@@ -1,5 +1,5 @@
#+private
package os2
package os
import "base:intrinsics"
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+build darwin, linux, netbsd, freebsd, openbsd, haiku
package os
package os_old
import "core:strings"

View File

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

View File

@@ -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..<len(env) {
if env[j] == '=' {
unset_env(env[0:j])
break
}
}
}
}

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

@@ -0,0 +1,318 @@
package os_old
import "base:intrinsics"
import "base:runtime"
import "core:io"
Platform_Error :: _Platform_Error
#assert(size_of(Platform_Error) <= 4)
#assert(intrinsics.type_has_nil(Platform_Error))
General_Error :: enum u32 {
None,
Exist,
Not_Exist,
Timeout,
Broken_Pipe,
Invalid_File,
Invalid_Dir,
Invalid_Path,
Invalid_Callback,
Pattern_Has_Separator,
File_Is_Pipe,
Not_Dir,
// Environment variable not found.
Env_Var_Not_Found,
}
Errno :: Error // alias for legacy use
Error :: union #shared_nil {
General_Error,
io.Error,
runtime.Allocator_Error,
Platform_Error,
}
#assert(size_of(Error) == 8)
ERROR_NONE :: Error{}
ERROR_EOF :: io.Error.EOF
@(require_results)
is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) {
v := ferr.(Platform_Error) or_else {}
return i32(v), i32(v) != 0
}
@(require_results)
error_string :: proc "contextless" (ferr: Error) -> string {
if ferr == nil {
return ""
}
switch e in ferr {
case General_Error:
switch e {
case .None: return ""
case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist"
case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe"
case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback"
case .Pattern_Has_Separator: return "pattern has separator"
case .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
}
case io.Error:
switch e {
case .None: return ""
case .EOF: return "eof"
case .Unexpected_EOF: return "unexpected eof"
case .Short_Write: return "short write"
case .Invalid_Write: return "invalid write result"
case .Short_Buffer: return "short buffer"
case .No_Progress: return "multiple read calls return no data or error"
case .Invalid_Whence: return "invalid whence"
case .Invalid_Offset: return "invalid offset"
case .Invalid_Unread: return "invalid unread"
case .Negative_Read: return "negative read"
case .Negative_Write: return "negative write"
case .Negative_Count: return "negative count"
case .Buffer_Full: return "buffer full"
case .Permission_Denied: return "permission denied"
case .Closed: return "file already closed"
case .No_Size: return "file has no definite size"
case .Unsupported: return "unsupported"
case .Unknown: //
}
case runtime.Allocator_Error:
switch e {
case .None: return ""
case .Out_Of_Memory: return "out of memory"
case .Invalid_Pointer: return "invalid allocator pointer"
case .Invalid_Argument: return "invalid allocator argument"
case .Mode_Not_Implemented: return "allocator mode not implemented"
}
case Platform_Error:
return _error_string(e)
}
return "unknown error"
}
print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) {
err_str := error_string(ferr)
// msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1
buf_ := intrinsics.alloca(length, 1)
buf := buf_[:length]
copy(buf, msg)
buf[len(msg)] = ':'
buf[len(msg) + 1] = ' '
copy(buf[len(msg) + 2:], err_str)
buf[length - 1] = '\n'
return write(f, buf)
}
@(require_results, private)
_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) {
if e == nil {
return ""
}
when ODIN_OS == .Darwin {
if s := string(_darwin_string_error(i32(e))); s != "" {
return s
}
}
when ODIN_OS != .Linux {
@(require_results)
binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check {
n := len(array)
left, right := 0, n
for left < right {
mid := int(uint(left+right) >> 1)
if array[mid] < key {
left = mid+1
} else {
// equal or greater
right = mid
}
}
return left, left < n && array[left] == key
}
err := runtime.Type_Info_Enum_Value(e)
ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum)
if idx, ok := binary_search(ti.values, err); ok {
return ti.names[idx]
}
} else {
@(rodata, static)
pe_strings := [Platform_Error]string{
.NONE = "",
.EPERM = "Operation not permitted",
.ENOENT = "No such file or directory",
.ESRCH = "No such process",
.EINTR = "Interrupted system call",
.EIO = "Input/output error",
.ENXIO = "No such device or address",
.E2BIG = "Argument list too long",
.ENOEXEC = "Exec format error",
.EBADF = "Bad file descriptor",
.ECHILD = "No child processes",
.EAGAIN = "Resource temporarily unavailable",
.ENOMEM = "Cannot allocate memory",
.EACCES = "Permission denied",
.EFAULT = "Bad address",
.ENOTBLK = "Block device required",
.EBUSY = "Device or resource busy",
.EEXIST = "File exists",
.EXDEV = "Invalid cross-device link",
.ENODEV = "No such device",
.ENOTDIR = "Not a directory",
.EISDIR = "Is a directory",
.EINVAL = "Invalid argument",
.ENFILE = "Too many open files in system",
.EMFILE = "Too many open files",
.ENOTTY = "Inappropriate ioctl for device",
.ETXTBSY = "Text file busy",
.EFBIG = "File too large",
.ENOSPC = "No space left on device",
.ESPIPE = "Illegal seek",
.EROFS = "Read-only file system",
.EMLINK = "Too many links",
.EPIPE = "Broken pipe",
.EDOM = "Numerical argument out of domain",
.ERANGE = "Numerical result out of range",
.EDEADLK = "Resource deadlock avoided",
.ENAMETOOLONG = "File name too long",
.ENOLCK = "No locks available",
.ENOSYS = "Function not implemented",
.ENOTEMPTY = "Directory not empty",
.ELOOP = "Too many levels of symbolic links",
.EUNKNOWN_41 = "Unknown Error (41)",
.ENOMSG = "No message of desired type",
.EIDRM = "Identifier removed",
.ECHRNG = "Channel number out of range",
.EL2NSYNC = "Level 2 not synchronized",
.EL3HLT = "Level 3 halted",
.EL3RST = "Level 3 reset",
.ELNRNG = "Link number out of range",
.EUNATCH = "Protocol driver not attached",
.ENOCSI = "No CSI structure available",
.EL2HLT = "Level 2 halted",
.EBADE = "Invalid exchange",
.EBADR = "Invalid request descriptor",
.EXFULL = "Exchange full",
.ENOANO = "No anode",
.EBADRQC = "Invalid request code",
.EBADSLT = "Invalid slot",
.EUNKNOWN_58 = "Unknown Error (58)",
.EBFONT = "Bad font file format",
.ENOSTR = "Device not a stream",
.ENODATA = "No data available",
.ETIME = "Timer expired",
.ENOSR = "Out of streams resources",
.ENONET = "Machine is not on the network",
.ENOPKG = "Package not installed",
.EREMOTE = "Object is remote",
.ENOLINK = "Link has been severed",
.EADV = "Advertise error",
.ESRMNT = "Srmount error",
.ECOMM = "Communication error on send",
.EPROTO = "Protocol error",
.EMULTIHOP = "Multihop attempted",
.EDOTDOT = "RFS specific error",
.EBADMSG = "Bad message",
.EOVERFLOW = "Value too large for defined data type",
.ENOTUNIQ = "Name not unique on network",
.EBADFD = "File descriptor in bad state",
.EREMCHG = "Remote address changed",
.ELIBACC = "Can not access a needed shared library",
.ELIBBAD = "Accessing a corrupted shared library",
.ELIBSCN = ".lib section in a.out corrupted",
.ELIBMAX = "Attempting to link in too many shared libraries",
.ELIBEXEC = "Cannot exec a shared library directly",
.EILSEQ = "Invalid or incomplete multibyte or wide character",
.ERESTART = "Interrupted system call should be restarted",
.ESTRPIPE = "Streams pipe error",
.EUSERS = "Too many users",
.ENOTSOCK = "Socket operation on non-socket",
.EDESTADDRREQ = "Destination address required",
.EMSGSIZE = "Message too long",
.EPROTOTYPE = "Protocol wrong type for socket",
.ENOPROTOOPT = "Protocol not available",
.EPROTONOSUPPORT = "Protocol not supported",
.ESOCKTNOSUPPORT = "Socket type not supported",
.EOPNOTSUPP = "Operation not supported",
.EPFNOSUPPORT = "Protocol family not supported",
.EAFNOSUPPORT = "Address family not supported by protocol",
.EADDRINUSE = "Address already in use",
.EADDRNOTAVAIL = "Cannot assign requested address",
.ENETDOWN = "Network is down",
.ENETUNREACH = "Network is unreachable",
.ENETRESET = "Network dropped connection on reset",
.ECONNABORTED = "Software caused connection abort",
.ECONNRESET = "Connection reset by peer",
.ENOBUFS = "No buffer space available",
.EISCONN = "Transport endpoint is already connected",
.ENOTCONN = "Transport endpoint is not connected",
.ESHUTDOWN = "Cannot send after transport endpoint shutdown",
.ETOOMANYREFS = "Too many references: cannot splice",
.ETIMEDOUT = "Connection timed out",
.ECONNREFUSED = "Connection refused",
.EHOSTDOWN = "Host is down",
.EHOSTUNREACH = "No route to host",
.EALREADY = "Operation already in progress",
.EINPROGRESS = "Operation now in progress",
.ESTALE = "Stale file handle",
.EUCLEAN = "Structure needs cleaning",
.ENOTNAM = "Not a XENIX named type file",
.ENAVAIL = "No XENIX semaphores available",
.EISNAM = "Is a named type file",
.EREMOTEIO = "Remote I/O error",
.EDQUOT = "Disk quota exceeded",
.ENOMEDIUM = "No medium found",
.EMEDIUMTYPE = "Wrong medium type",
.ECANCELED = "Operation canceled",
.ENOKEY = "Required key not available",
.EKEYEXPIRED = "Key has expired",
.EKEYREVOKED = "Key has been revoked",
.EKEYREJECTED = "Key was rejected by service",
.EOWNERDEAD = "Owner died",
.ENOTRECOVERABLE = "State not recoverable",
.ERFKILL = "Operation not possible due to RF-kill",
.EHWPOISON = "Memory page has hardware error",
}
if Platform_Error.NONE <= e && e <= max(Platform_Error) {
return pe_strings[e]
}
}
return "<unknown platform error>"
}
@(private, require_results)
error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None
}
return ferr.(io.Error) or_else .Unknown
}

View File

@@ -1,5 +1,5 @@
// Cross-platform `OS` interactions like file `I/O`.
package os
package os_old
import "base:intrinsics"
import "base:runtime"

View File

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

View File

@@ -1,4 +1,4 @@
package os
package os_old
import "core:sys/es"

View File

@@ -1,4 +1,4 @@
package os
package os_old
foreign import dl "system:dl"
foreign import libc "system:c"

View File

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

View File

@@ -1,4 +1,4 @@
package os
package os_old
foreign import lib "system:c"

View File

@@ -1,5 +1,5 @@
#+build js
package os
package os_old
foreign import "odin_env"

View File

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

View File

@@ -1,4 +1,4 @@
package os
package os_old
foreign import dl "system:dl"
foreign import libc "system:c"

View File

@@ -1,4 +1,4 @@
package os
package os_old
foreign import libc "system:c"

View File

@@ -1,4 +1,4 @@
package os
package os_old
import "core:sys/wasm/wasi"
import "base:runtime"

View File

@@ -1,5 +1,5 @@
#+build windows
package os
package os_old
import win32 "core:sys/windows"
import "base:runtime"

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

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

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