Files
Odin/core/c/libc/stdio.odin
T
BBandClaude Opus 5.5 baca00868b Pass va_list by value to libc's v* functions
C passes a va_list argument by value. The bindings took ^va_list, which
only matches x86-64 System V, where va_list is an array that decays to
a pointer. On Darwin, Windows and the other targets va_list is a
pointer or a struct, so vsnprintf and friends received the address of
the list instead of the list and printed or scanned garbage.

The foreign declarations now use the platform's form, and the public
procedures keep taking ^va_list so callers stay portable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 22:10:02 +02:00

409 lines
9.6 KiB
Odin

package libc
import "core:c"
import "core:io"
when ODIN_OS == .Windows {
foreign import libc {
"system:libucrt.lib",
"system:legacy_stdio_definitions.lib",
}
} else when ODIN_OS == .Darwin {
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
// 7.21 Input/output
FILE :: c.FILE
Whence :: enum int {
SET = SEEK_SET,
CUR = SEEK_CUR,
END = SEEK_END,
}
// MSVCRT compatible.
when ODIN_OS == .Windows {
_IOFBF :: 0x0000
_IONBF :: 0x0004
_IOLBF :: 0x0040
BUFSIZ :: 512
EOF :: int(-1)
FOPEN_MAX :: 20
FILENAME_MAX :: 260
L_tmpnam :: 15 // `"\\" + 12 + NUL`
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 32767 // SHRT_MAX
fpos_t :: distinct i64
@(private="file")
@(default_calling_convention="c")
foreign libc {
__acrt_iob_func :: proc (index: uint) -> ^FILE ---
}
stdin := __acrt_iob_func(0)
stdout := __acrt_iob_func(1)
stderr := __acrt_iob_func(2)
}
// GLIBC and MUSL compatible.
when ODIN_OS == .Linux {
fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, }
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 2
BUFSIZ :: 1024
EOF :: int(-1)
FOPEN_MAX :: 1000
FILENAME_MAX :: 4096
L_tmpnam :: 20
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 308915776
foreign libc {
stderr: ^FILE
stdin: ^FILE
stdout: ^FILE
}
}
when ODIN_OS == .JS {
fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, }
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 2
BUFSIZ :: 1024
EOF :: int(-1)
FOPEN_MAX :: 1000
FILENAME_MAX :: 4096
L_tmpnam :: 20
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 308915776
}
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
fpos_t :: distinct i64
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 1
BUFSIZ :: 1024
EOF :: int(-1)
FOPEN_MAX :: 20
FILENAME_MAX :: 1024
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 308915776
foreign libc {
__sF: [3]FILE
}
stdin: ^FILE = &__sF[0]
stdout: ^FILE = &__sF[1]
stderr: ^FILE = &__sF[2]
}
when ODIN_OS == .FreeBSD {
fpos_t :: distinct i64
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 1
BUFSIZ :: 1024
EOF :: int(-1)
FOPEN_MAX :: 20
FILENAME_MAX :: 1024
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 308915776
foreign libc {
@(link_name="__stderrp") stderr: ^FILE
@(link_name="__stdinp") stdin: ^FILE
@(link_name="__stdoutp") stdout: ^FILE
}
}
when ODIN_OS == .Darwin {
fpos_t :: distinct i64
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 2
BUFSIZ :: 1024
EOF :: int(-1)
FOPEN_MAX :: 20
FILENAME_MAX :: 1024
L_tmpnam :: 1024
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 308915776
foreign libc {
@(link_name="__stderrp") stderr: ^FILE
@(link_name="__stdinp") stdin: ^FILE
@(link_name="__stdoutp") stdout: ^FILE
}
}
when ODIN_OS == .NetBSD {
@(private) LRENAME :: "__posix_rename"
@(private) LFGETPOS :: "__fgetpos50"
@(private) LFSETPOS :: "__fsetpos50"
} else {
@(private) LRENAME :: "rename"
@(private) LFGETPOS :: "fgetpos"
@(private) LFSETPOS :: "fsetpos"
}
@(default_calling_convention="c")
foreign libc {
// 7.21.4 Operations on files
remove :: proc(filename: cstring) -> int ---
@(link_name=LRENAME)
rename :: proc(old, new: cstring) -> int ---
tmpfile :: proc() -> ^FILE ---
tmpnam :: proc(s: [^]char) -> [^]char ---
// 7.21.5 File access functions
fclose :: proc(stream: ^FILE) -> int ---
fflush :: proc(stream: ^FILE) -> int ---
fopen :: proc(filename, mode: cstring) -> ^FILE ---
freopen :: proc(filename, mode: cstring, stream: ^FILE) -> ^FILE ---
setbuf :: proc(stream: ^FILE, buf: [^]char) ---
setvbuf :: proc(stream: ^FILE, buf: [^]char, mode: int, size: size_t) -> int ---
// 7.21.6 Formatted input/output functions
fprintf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---
fscanf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---
printf :: proc(format: cstring, #c_vararg args: ..any) -> int ---
scanf :: proc(format: cstring, #c_vararg args: ..any) -> int ---
snprintf :: proc(s: [^]char, n: size_t, format: cstring, #c_vararg args: ..any) -> int ---
sscanf :: proc(s, format: cstring, #c_vararg args: ..any) -> int ---
// 7.21.7 Character input/output functions
fgetc :: proc(stream: ^FILE) -> int ---
fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char ---
fputc :: proc(s: c.int, stream: ^FILE) -> int ---
getc :: proc(stream: ^FILE) -> int ---
getchar :: proc() -> int ---
putc :: proc(c: int, stream: ^FILE) -> int ---
putchar :: proc(c: int) -> int ---
puts :: proc(s: cstring) -> int ---
ungetc :: proc(c: int, stream: ^FILE) -> int ---
fread :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
// 7.21.9 File positioning functions
@(link_name=LFGETPOS)
fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
fseek :: proc(stream: ^FILE, offset: long, whence: Whence) -> int ---
@(link_name=LFSETPOS)
fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
ftell :: proc(stream: ^FILE) -> long ---
rewind :: proc(stream: ^FILE) ---
// 7.21.10 Error-handling functions
clearerr :: proc(stream: ^FILE) ---
feof :: proc(stream: ^FILE) -> int ---
ferror :: proc(stream: ^FILE) -> int ---
perror :: proc(s: cstring) ---
}
to_stream :: proc(file: ^FILE) -> io.Stream {
stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
unknown_or_eof :: proc(f: ^FILE) -> io.Error {
switch {
case ferror(f) != 0:
return .Unknown
case feof(f) != 0:
return .EOF
case:
return nil
}
}
file := (^FILE)(stream_data)
switch mode {
case .Close:
if fclose(file) != 0 {
return 0, unknown_or_eof(file)
}
case .Flush:
if fflush(file) != 0 {
return 0, unknown_or_eof(file)
}
case .Read:
n = i64(fread(raw_data(p), size_of(byte), len(p), file))
if n == 0 { err = unknown_or_eof(file) }
case .Read_At:
curr := ftell(file)
if curr == -1 {
return 0, unknown_or_eof(file)
}
if fseek(file, long(offset), .SET) != 0 {
return 0, unknown_or_eof(file)
}
defer fseek(file, long(curr), .SET)
n = i64(fread(raw_data(p), size_of(byte), len(p), file))
if n == 0 { err = unknown_or_eof(file) }
case .Write:
n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
if n == 0 { err = unknown_or_eof(file) }
case .Write_At:
curr := ftell(file)
if curr == -1 {
return 0, unknown_or_eof(file)
}
if fseek(file, long(offset), .SET) != 0 {
return 0, unknown_or_eof(file)
}
defer fseek(file, long(curr), .SET)
n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
if n == 0 { err = unknown_or_eof(file) }
case .Seek:
#assert(int(Whence.SET) == int(io.Seek_From.Start))
#assert(int(Whence.CUR) == int(io.Seek_From.Current))
#assert(int(Whence.END) == int(io.Seek_From.End))
if fseek(file, long(offset), Whence(whence)) != 0 {
return 0, unknown_or_eof(file)
}
case .Size:
curr := ftell(file)
if curr == -1 {
return 0, unknown_or_eof(file)
}
defer fseek(file, curr, .SET)
if fseek(file, 0, .END) != 0 {
return 0, unknown_or_eof(file)
}
n = i64(ftell(file))
if n == -1 {
return 0, unknown_or_eof(file)
}
case .Destroy:
return 0, .Unsupported
case .Query:
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })
}
return
}
return {
data = file,
procedure = stream_proc,
}
}
// The v* procedures take a pointer to the va_list on every target, see va_list_arg.
@(default_calling_convention="c", private)
foreign libc {
@(link_name="vfprintf") _vfprintf :: proc(stream: ^FILE, format: cstring, arg: va_list_arg) -> int ---
@(link_name="vfscanf") _vfscanf :: proc(stream: ^FILE, format: cstring, arg: va_list_arg) -> int ---
@(link_name="vprintf") _vprintf :: proc(format: cstring, arg: va_list_arg) -> int ---
@(link_name="vscanf") _vscanf :: proc(format: cstring, arg: va_list_arg) -> int ---
@(link_name="vsnprintf") _vsnprintf :: proc(s: [^]char, n: size_t, format: cstring, arg: va_list_arg) -> int ---
@(link_name="vsprintf") _vsprintf :: proc(s: [^]char, format: cstring, arg: va_list_arg) -> int ---
@(link_name="vsscanf") _vsscanf :: proc(s, format: cstring, arg: va_list_arg) -> int ---
}
vfprintf :: #force_inline proc "c" (stream: ^FILE, format: cstring, arg: ^va_list) -> int {
return _vfprintf(stream, format, va_list_arg_from(arg))
}
vfscanf :: #force_inline proc "c" (stream: ^FILE, format: cstring, arg: ^va_list) -> int {
return _vfscanf(stream, format, va_list_arg_from(arg))
}
vprintf :: #force_inline proc "c" (format: cstring, arg: ^va_list) -> int {
return _vprintf(format, va_list_arg_from(arg))
}
vscanf :: #force_inline proc "c" (format: cstring, arg: ^va_list) -> int {
return _vscanf(format, va_list_arg_from(arg))
}
vsnprintf :: #force_inline proc "c" (s: [^]char, n: size_t, format: cstring, arg: ^va_list) -> int {
return _vsnprintf(s, n, format, va_list_arg_from(arg))
}
vsprintf :: #force_inline proc "c" (s: [^]char, format: cstring, arg: ^va_list) -> int {
return _vsprintf(s, format, va_list_arg_from(arg))
}
vsscanf :: #force_inline proc "c" (s, format: cstring, arg: ^va_list) -> int {
return _vsscanf(s, format, va_list_arg_from(arg))
}