Browse Source

Merge branch 'master' into os2-process-linux

jasonKercher 1 year ago
parent
commit
215b21811e

+ 28 - 17
core/fmt/fmt.odin

@@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
 	message := tprintf(fmt, ..args)
 	message := tprintf(fmt, ..args)
 	p("Panic", message, loc)
 	p("Panic", message, loc)
 }
 }
+
+// 	Creates a formatted C string
+//
+// 	*Allocates Using Context's Allocator*
+//
+// 	Inputs:
+// 	- args: A variadic list of arguments to be formatted.
+// 	- sep: An optional separator string (default is a single space).
+//
+// 	Returns: A formatted C string.
+//
+@(require_results)
+caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring {
+	str: strings.Builder
+	strings.builder_init(&str, allocator)
+	sbprint(&str, ..args, sep=sep)
+	strings.write_byte(&str, 0)
+	s := strings.to_string(str)
+	return cstring(raw_data(s))
+}
+
 // Creates a formatted C string
 // Creates a formatted C string
 //
 //
 // *Allocates Using Context's Allocator*
 // *Allocates Using Context's Allocator*
@@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
 // Returns: A formatted C string
 // Returns: A formatted C string
 //
 //
 @(require_results)
 @(require_results)
-caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
+caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring {
 	str: strings.Builder
 	str: strings.Builder
-	strings.builder_init(&str)
+	strings.builder_init(&str, allocator)
 	sbprintf(&str, format, ..args, newline=newline)
 	sbprintf(&str, format, ..args, newline=newline)
 	strings.write_byte(&str, 0)
 	strings.write_byte(&str, 0)
 	s := strings.to_string(str)
 	s := strings.to_string(str)
@@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 // Returns: A formatted C string
 // Returns: A formatted C string
 //
 //
 @(require_results)
 @(require_results)
-caprintfln :: proc(format: string, args: ..any) -> cstring {
-	return caprintf(format, ..args, newline=true)
+caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring {
+	return caprintf(format, ..args, allocator=allocator, newline=true)
 }
 }
 // 	Creates a formatted C string
 // 	Creates a formatted C string
 //
 //
@@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprint :: proc(args: ..any, sep := " ") -> cstring {
 ctprint :: proc(args: ..any, sep := " ") -> cstring {
-	str: strings.Builder
-	strings.builder_init(&str, context.temp_allocator)
-	sbprint(&str, ..args, sep=sep)
-	strings.write_byte(&str, 0)
-	s := strings.to_string(str)
-	return cstring(raw_data(s))
+	return caprint(args=args, sep=sep, allocator=context.temp_allocator)
 }
 }
 // Creates a formatted C string
 // Creates a formatted C string
 //
 //
@@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
-	str: strings.Builder
-	strings.builder_init(&str, context.temp_allocator)
-	sbprintf(&str, format, ..args, newline=newline)
-	strings.write_byte(&str, 0)
-	s := strings.to_string(str)
-	return cstring(raw_data(s))
+	return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline)
 }
 }
 // Creates a formatted C string, followed by a newline.
 // Creates a formatted C string, followed by a newline.
 //
 //
@@ -419,7 +430,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring {
 //
 //
 @(require_results)
 @(require_results)
 ctprintfln :: proc(format: string, args: ..any) -> cstring {
 ctprintfln :: proc(format: string, args: ..any) -> cstring {
-	return ctprintf(format, ..args, newline=true)
+	return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true)
 }
 }
 // Formats using the default print settings and writes to the given strings.Builder
 // Formats using the default print settings and writes to the given strings.Builder
 //
 //

+ 62 - 3
core/os/os2/dir.odin

@@ -1,21 +1,80 @@
 package os2
 package os2
 
 
 import "base:runtime"
 import "base:runtime"
+import "core:slice"
 
 
-read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
-	return _read_directory(f, n, allocator)
+@(require_results)
+read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
+	if f == nil {
+		return nil, .Invalid_File
+	}
+
+	n := n
+	size := n
+	if n <= 0 {
+		n = -1
+		size = 100
+	}
+
+	TEMP_ALLOCATOR_GUARD()
+
+	it := read_directory_iterator_create(f) or_return
+	defer _read_directory_iterator_destroy(&it)
+
+	dfi := make([dynamic]File_Info, 0, size, temp_allocator())
+	defer if err != nil {
+		for fi in dfi {
+			file_info_delete(fi, allocator)
+		}
+	}
+
+	for fi, index in read_directory_iterator(&it) {
+		if n > 0 && index == n {
+			break
+		}
+		append(&dfi, file_info_clone(fi, allocator) or_return)
+	}
+
+	return slice.clone(dfi[:], allocator)
 }
 }
 
 
+
+@(require_results)
 read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 	return read_directory(f, -1, allocator)
 	return read_directory(f, -1, allocator)
 }
 }
 
 
+@(require_results)
 read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 	f := open(path) or_return
 	f := open(path) or_return
 	defer close(f)
 	defer close(f)
 	return read_directory(f, n, allocator)
 	return read_directory(f, n, allocator)
 }
 }
 
 
+@(require_results)
 read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) {
 	return read_directory_by_path(path, -1, allocator)
 	return read_directory_by_path(path, -1, allocator)
-}
+}
+
+
+Read_Directory_Iterator :: struct {
+	f:    ^File,
+	impl: Read_Directory_Iterator_Impl,
+}
+
+
+@(require_results)
+read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
+	return _read_directory_iterator_create(f)
+}
+
+read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+	_read_directory_iterator_destroy(it)
+}
+
+
+// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone`
+@(require_results)
+read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
+	return _read_directory_iterator(it)
+}

+ 15 - 3
core/os/os2/dir_linux.odin

@@ -1,8 +1,20 @@
+//+private
 package os2
 package os2
 
 
-import "base:runtime"
+Read_Directory_Iterator_Impl :: struct {
 
 
-@(private)
-_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
+}
+
+
+@(require_results)
+_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
 	return
 	return
 }
 }
+
+@(require_results)
+_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
+	return {}, nil
+}
+
+_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+}

+ 86 - 76
core/os/os2/dir_windows.odin

@@ -1,67 +1,92 @@
+//+private
 package os2
 package os2
 
 
 import "base:runtime"
 import "base:runtime"
 import "core:time"
 import "core:time"
 import win32 "core:sys/windows"
 import win32 "core:sys/windows"
 
 
-@(private)
-_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) {
-	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 {
+@(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
+	}
+	path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
+
+
+	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, nil, d.dwReserved0)
+
+	// fi.inode             = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
+	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))
+	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,
+	index:         int,
+}
+
+
+@(require_results)
+_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
+	if it.f == nil {
+		return
+	}
+
+	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 {
 			return
 			return
 		}
 		}
-		path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
-		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.impl.index
+			it.impl.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 !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) {
+				it.impl.no_more_files = true
+			}
+			it.impl.no_more_files = true
 		}
 		}
-
-		if is_sym {
-			fi.type = .Symlink
-		} else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 {
-			fi.type = .Directory
-			fi.mode |= 0o111
+		if ok {
+			return
 		}
 		}
-
-		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))
-		return
 	}
 	}
+	return
+}
 
 
+@(require_results)
+_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
 	if f == nil {
 	if f == nil {
-		return nil, .Invalid_File
+		return
 	}
 	}
-
-	TEMP_ALLOCATOR_GUARD()
-
+	it.f = f
 	impl := (^File_Impl)(f.impl)
 	impl := (^File_Impl)(f.impl)
 
 
 	if !is_directory(impl.name) {
 	if !is_directory(impl.name) {
-		return nil, .Invalid_Dir
-	}
-
-	n := n
-	size := n
-	if n <= 0 {
-		n = -1
-		size = 100
+		err = .Invalid_Dir
+		return
 	}
 	}
 
 
 	wpath: []u16
 	wpath: []u16
@@ -73,46 +98,31 @@ _read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (file
 		wpath = impl.wname[:i]
 		wpath = impl.wname[:i]
 	}
 	}
 
 
+	TEMP_ALLOCATOR_GUARD()
 
 
-	wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
+	wpath_search := make([]u16, len(wpath)+3, temp_allocator())
 	copy(wpath_search, wpath)
 	copy(wpath_search, wpath)
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+0] = '\\'
 	wpath_search[len(wpath)+1] = '*'
 	wpath_search[len(wpath)+1] = '*'
 	wpath_search[len(wpath)+2] = 0
 	wpath_search[len(wpath)+2] = 0
 
 
-	find_data := &win32.WIN32_FIND_DATAW{}
-	find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data)
-	if find_handle == win32.INVALID_HANDLE_VALUE {
-		return nil, _get_platform_error()
+	it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data)
+	if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
+		err = _get_platform_error()
+		return
 	}
 	}
-	defer win32.FindClose(find_handle)
-
-	path := _cleanpath_from_buf(wpath, temp_allocator()) or_return
-
-	dfi := make([dynamic]File_Info, 0, size, allocator)
 	defer if err != nil {
 	defer if err != nil {
-		for fi in dfi {
-			file_info_delete(fi, allocator)
-		}
-		delete(dfi)
-	}
-	for n != 0 {
-		fi: File_Info
-		fi = find_data_to_file_info(path, find_data, allocator) or_return
-		if fi.name != "" {
-			append(&dfi, fi)
-			n -= 1
-		}
-
-		if !win32.FindNextFileW(find_handle, find_data) {
-			e := _get_platform_error()
-			if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
-				break
-			}
-			return dfi[:], e
-		}
+		win32.FindClose(it.impl.find_handle)
 	}
 	}
 
 
-	return dfi[:], nil
+	it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return
+	return
 }
 }
 
 
+_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
+	if it.f == nil {
+		return
+	}
+	file_info_delete(it.impl.prev_fi, file_allocator())
+	win32.FindClose(it.impl.find_handle)
+}

+ 1 - 1
core/os/os2/file.odin

@@ -106,7 +106,7 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File,
 
 
 @(require_results)
 @(require_results)
 new_file :: proc(handle: uintptr, name: string) -> ^File {
 new_file :: proc(handle: uintptr, name: string) -> ^File {
-	return _new_file(handle, name)
+	return _new_file(handle, name) or_else panic("Out of memory")
 }
 }
 
 
 @(require_results)
 @(require_results)

+ 8 - 5
core/os/os2/file_linux.odin

@@ -102,21 +102,24 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
 		return nil, _get_platform_error(errno)
 		return nil, _get_platform_error(errno)
 	}
 	}
 
 
-	return _new_file(uintptr(fd), name), nil
+	return _new_file(uintptr(fd), name)
 }
 }
 
 
-_new_file :: proc(fd: uintptr, _: string = "") -> ^File {
-	impl := new(File_Impl, file_allocator())
+_new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) {
+	impl := new(File_Impl, file_allocator()) or_return
+	defer if err != nil {
+		free(impl, file_allocator())
+	}
 	impl.file.impl = impl
 	impl.file.impl = impl
 	impl.fd = linux.Fd(fd)
 	impl.fd = linux.Fd(fd)
 	impl.allocator = file_allocator()
 	impl.allocator = file_allocator()
-	impl.name = _get_full_path(impl.fd, impl.allocator)
+	impl.name = _get_full_path(impl.fd, file_allocator()) or_return
 	impl.file.stream = {
 	impl.file.stream = {
 		data = impl,
 		data = impl,
 		procedure = _file_stream_proc,
 		procedure = _file_stream_proc,
 	}
 	}
 	impl.file.fstat = _fstat
 	impl.file.fstat = _fstat
-	return &impl.file
+	return &impl.file, nil
 }
 }
 
 
 _destroy :: proc(f: ^File_Impl) -> Error {
 _destroy :: proc(f: ^File_Impl) -> Error {

+ 12 - 8
core/os/os2/file_windows.odin

@@ -94,7 +94,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 		create_mode = win32.TRUNCATE_EXISTING
 		create_mode = win32.TRUNCATE_EXISTING
 	}
 	}
 
 
-	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL
+	attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS
 	if perm & S_IWRITE == 0 {
 	if perm & S_IWRITE == 0 {
 		attrs = win32.FILE_ATTRIBUTE_READONLY
 		attrs = win32.FILE_ATTRIBUTE_READONLY
 		if create_mode == win32.CREATE_ALWAYS {
 		if create_mode == win32.CREATE_ALWAYS {
@@ -126,20 +126,24 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
 	flags := flags if flags != nil else {.Read}
 	flags := flags if flags != nil else {.Read}
 	handle := _open_internal(name, flags, perm) or_return
 	handle := _open_internal(name, flags, perm) or_return
-	return _new_file(handle, name), nil
+	return _new_file(handle, name)
 }
 }
 
 
-_new_file :: proc(handle: uintptr, name: string) -> ^File {
+_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) {
 	if handle == INVALID_HANDLE {
 	if handle == INVALID_HANDLE {
-		return nil
+		return
 	}
 	}
-	impl := new(File_Impl, file_allocator())
+	impl := new(File_Impl, file_allocator()) or_return
+	defer if err != nil {
+		free(impl, file_allocator())
+	}
+
 	impl.file.impl = impl
 	impl.file.impl = impl
 
 
 	impl.allocator = file_allocator()
 	impl.allocator = file_allocator()
 	impl.fd = rawptr(handle)
 	impl.fd = rawptr(handle)
-	impl.name, _ = clone_string(name, impl.allocator)
-	impl.wname, _ = win32_utf8_to_wstring(name, impl.allocator)
+	impl.name = clone_string(name, impl.allocator) or_return
+	impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return
 
 
 	handle := _handle(&impl.file)
 	handle := _handle(&impl.file)
 	kind := File_Impl_Kind.File
 	kind := File_Impl_Kind.File
@@ -157,7 +161,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File {
 	}
 	}
 	impl.file.fstat = _fstat
 	impl.file.fstat = _fstat
 
 
-	return &impl.file
+	return &impl.file, nil
 }
 }
 
 
 _fd :: proc(f: ^File) -> uintptr {
 _fd :: proc(f: ^File) -> uintptr {

+ 0 - 4
core/os/os2/heap.odin

@@ -17,7 +17,3 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode
                             old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) {
                             old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) {
 	return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc)
 	return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc)
 }
 }
-
-
-@(private)
-error_allocator := heap_allocator

+ 4 - 5
core/os/os2/path_linux.odin

@@ -188,7 +188,7 @@ _set_working_directory :: proc(dir: string) -> Error {
 	return _get_platform_error(linux.chdir(dir_cstr))
 	return _get_platform_error(linux.chdir(dir_cstr))
 }
 }
 
 
-_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
+_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) {
 	PROC_FD_PATH :: "/proc/self/fd/"
 	PROC_FD_PATH :: "/proc/self/fd/"
 
 
 	buf: [32]u8
 	buf: [32]u8
@@ -196,10 +196,9 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string {
 
 
 	strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
 	strconv.itoa(buf[len(PROC_FD_PATH):], int(fd))
 
 
-	fullpath: string
-	err: Error
 	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
 	if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' {
-		return ""
+		delete(fullpath, allocator)
+		fullpath = ""
 	}
 	}
-	return fullpath
+	return
 }
 }

+ 14 - 1
core/os/os2/stat.odin

@@ -1,21 +1,34 @@
 package os2
 package os2
 
 
-import "core:time"
 import "base:runtime"
 import "base:runtime"
+import "core:path/filepath"
+import "core:strings"
+import "core:time"
 
 
 Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
 Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error)
 
 
 File_Info :: struct {
 File_Info :: struct {
 	fullpath:          string,
 	fullpath:          string,
 	name:              string,
 	name:              string,
+
+	inode:             u128, // might be zero if cannot be determined
 	size:              i64,
 	size:              i64,
 	mode:              int,
 	mode:              int,
 	type:              File_Type,
 	type:              File_Type,
+
 	creation_time:     time.Time,
 	creation_time:     time.Time,
 	modification_time: time.Time,
 	modification_time: time.Time,
 	access_time:       time.Time,
 	access_time:       time.Time,
 }
 }
 
 
+@(require_results)
+file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) {
+	cloned = fi
+	cloned.fullpath = strings.clone(fi.fullpath) or_return
+	cloned.name = filepath.base(cloned.fullpath)
+	return
+}
+
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
 file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) {
 	for i := len(infos)-1; i >= 0; i -= 1 {
 	for i := len(infos)-1; i >= 0; i -= 1 {
 		file_info_delete(infos[i], allocator)
 		file_info_delete(infos[i], allocator)

+ 12 - 10
core/os/os2/stat_linux.odin

@@ -11,7 +11,7 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) {
 	return _fstat_internal(impl.fd, allocator)
 	return _fstat_internal(impl.fd, allocator)
 }
 }
 
 
-_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) {
+_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
 	s: linux.Stat
 	s: linux.Stat
 	errno := linux.fstat(fd, &s)
 	errno := linux.fstat(fd, &s)
 	if errno != .NONE {
 	if errno != .NONE {
@@ -28,20 +28,22 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Inf
 	case linux.S_IFSOCK: type = .Socket
 	case linux.S_IFSOCK: type = .Socket
 	}
 	}
 	mode := int(0o7777 & transmute(u32)s.mode)
 	mode := int(0o7777 & transmute(u32)s.mode)
+
 	// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
 	// TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time
-	fi := File_Info {
-		fullpath = _get_full_path(fd, allocator),
-		name = "",
-		size = i64(s.size),
-		mode = mode,
-		type = type,
+	fi = File_Info {
+		fullpath          = _get_full_path(fd, allocator) or_return,
+		name              = "",
+		inode             = u128(u64(s.ino)),
+		size              = i64(s.size),
+		mode              = mode,
+		type              = type,
 		modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
 		modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)},
-		access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
-		creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
+		access_time       = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)},
+		creation_time     = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this
 	}
 	}
 	fi.creation_time = fi.modification_time
 	fi.creation_time = fi.modification_time
 	fi.name = filepath.base(fi.fullpath)
 	fi.name = filepath.base(fi.fullpath)
-	return fi, nil
+	return
 }
 }
 
 
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
 // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath

+ 2 - 1
core/os/os2/stat_windows.odin

@@ -262,7 +262,8 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
 	fi: File_Info
 	fi: File_Info
 	fi.fullpath = path
 	fi.fullpath = path
 	fi.name = basename(path)
 	fi.name = basename(path)
-	fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
+	fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow))
+	fi.size  = i64(d.nFileSizeHigh)<<32  + i64(d.nFileSizeLow)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
 	fi.type = type
 	fi.type = type
 	fi.mode |= mode
 	fi.mode |= mode

+ 107 - 1
core/sys/windows/ntdll.odin

@@ -15,10 +15,27 @@ foreign ntdll_lib {
 		ProcessInformationLength: u32,
 		ProcessInformationLength: u32,
 		ReturnLength:             ^u32,
 		ReturnLength:             ^u32,
 	) -> u32 ---
 	) -> u32 ---
+
+	NtQueryInformationFile :: proc(
+		FileHandle:           HANDLE,
+		IoStatusBlock:        PIO_STATUS_BLOCK,
+		FileInformation:      rawptr,
+		Length:               ULONG,
+		FileInformationClass: FILE_INFORMATION_CLASS,
+	) -> NTSTATUS ---
+}
+
+PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK
+IO_STATUS_BLOCK :: struct {
+	using _: struct #raw_union {
+		Status:  NTSTATUS,
+		Pointer: rawptr,
+	},
+	Information: ULONG_PTR,
 }
 }
 
 
 
 
-PROCESS_INFO_CLASS :: enum i32 {
+PROCESS_INFO_CLASS :: enum c_int {
 	ProcessBasicInformation       = 0,
 	ProcessBasicInformation       = 0,
 	ProcessDebugPort              = 7,
 	ProcessDebugPort              = 7,
 	ProcessWow64Information       = 26,
 	ProcessWow64Information       = 26,
@@ -29,6 +46,95 @@ PROCESS_INFO_CLASS :: enum i32 {
 }
 }
 
 
 
 
+PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS
+FILE_INFORMATION_CLASS :: enum c_int {
+	FileDirectoryInformation                     = 1,
+	FileFullDirectoryInformation                 = 2,
+	FileBothDirectoryInformation                 = 3,
+	FileBasicInformation                         = 4,
+	FileStandardInformation                      = 5,
+	FileInternalInformation                      = 6,
+	FileEaInformation                            = 7,
+	FileAccessInformation                        = 8,
+	FileNameInformation                          = 9,
+	FileRenameInformation                        = 10,
+	FileLinkInformation                          = 11,
+	FileNamesInformation                         = 12,
+	FileDispositionInformation                   = 13,
+	FilePositionInformation                      = 14,
+	FileFullEaInformation                        = 15,
+	FileModeInformation                          = 16,
+	FileAlignmentInformation                     = 17,
+	FileAllInformation                           = 18,
+	FileAllocationInformation                    = 19,
+	FileEndOfFileInformation                     = 20,
+	FileAlternateNameInformation                 = 21,
+	FileStreamInformation                        = 22,
+	FilePipeInformation                          = 23,
+	FilePipeLocalInformation                     = 24,
+	FilePipeRemoteInformation                    = 25,
+	FileMailslotQueryInformation                 = 26,
+	FileMailslotSetInformation                   = 27,
+	FileCompressionInformation                   = 28,
+	FileObjectIdInformation                      = 29,
+	FileCompletionInformation                    = 30,
+	FileMoveClusterInformation                   = 31,
+	FileQuotaInformation                         = 32,
+	FileReparsePointInformation                  = 33,
+	FileNetworkOpenInformation                   = 34,
+	FileAttributeTagInformation                  = 35,
+	FileTrackingInformation                      = 36,
+	FileIdBothDirectoryInformation               = 37,
+	FileIdFullDirectoryInformation               = 38,
+	FileValidDataLengthInformation               = 39,
+	FileShortNameInformation                     = 40,
+	FileIoCompletionNotificationInformation      = 41,
+	FileIoStatusBlockRangeInformation            = 42,
+	FileIoPriorityHintInformation                = 43,
+	FileSfioReserveInformation                   = 44,
+	FileSfioVolumeInformation                    = 45,
+	FileHardLinkInformation                      = 46,
+	FileProcessIdsUsingFileInformation           = 47,
+	FileNormalizedNameInformation                = 48,
+	FileNetworkPhysicalNameInformation           = 49,
+	FileIdGlobalTxDirectoryInformation           = 50,
+	FileIsRemoteDeviceInformation                = 51,
+	FileUnusedInformation                        = 52,
+	FileNumaNodeInformation                      = 53,
+	FileStandardLinkInformation                  = 54,
+	FileRemoteProtocolInformation                = 55,
+	FileRenameInformationBypassAccessCheck       = 56,
+	FileLinkInformationBypassAccessCheck         = 57,
+	FileVolumeNameInformation                    = 58,
+	FileIdInformation                            = 59,
+	FileIdExtdDirectoryInformation               = 60,
+	FileReplaceCompletionInformation             = 61,
+	FileHardLinkFullIdInformation                = 62,
+	FileIdExtdBothDirectoryInformation           = 63,
+	FileDispositionInformationEx                 = 64,
+	FileRenameInformationEx                      = 65,
+	FileRenameInformationExBypassAccessCheck     = 66,
+	FileDesiredStorageClassInformation           = 67,
+	FileStatInformation                          = 68,
+	FileMemoryPartitionInformation               = 69,
+	FileStatLxInformation                        = 70,
+	FileCaseSensitiveInformation                 = 71,
+	FileLinkInformationEx                        = 72,
+	FileLinkInformationExBypassAccessCheck       = 73,
+	FileStorageReserveIdInformation              = 74,
+	FileCaseSensitiveInformationForceAccessCheck = 75,
+	FileKnownFolderInformation                   = 76,
+	FileStatBasicInformation                     = 77,
+	FileId64ExtdDirectoryInformation             = 78,
+	FileId64ExtdBothDirectoryInformation         = 79,
+	FileIdAllExtdDirectoryInformation            = 80,
+	FileIdAllExtdBothDirectoryInformation        = 81,
+	FileStreamReservationInformation,
+	FileMupProviderInfo,
+	FileMaximumInformation,
+}
+
+
 
 
 PROCESS_BASIC_INFORMATION :: struct {
 PROCESS_BASIC_INFORMATION :: struct {
 	ExitStatus:                   NTSTATUS,
 	ExitStatus:                   NTSTATUS,

+ 39 - 27
core/sys/windows/types.odin

@@ -1027,16 +1027,28 @@ TRACKMOUSEEVENT :: struct {
 }
 }
 
 
 WIN32_FIND_DATAW :: struct {
 WIN32_FIND_DATAW :: struct {
-	dwFileAttributes: DWORD,
-	ftCreationTime: FILETIME,
-	ftLastAccessTime: FILETIME,
-	ftLastWriteTime: FILETIME,
-	nFileSizeHigh: DWORD,
-	nFileSizeLow: DWORD,
-	dwReserved0: DWORD,
-	dwReserved1: DWORD,
-	cFileName: [260]wchar_t, // #define MAX_PATH 260
-	cAlternateFileName: [14]wchar_t,
+	dwFileAttributes:   DWORD,
+	ftCreationTime:     FILETIME,
+	ftLastAccessTime:   FILETIME,
+	ftLastWriteTime:    FILETIME,
+	nFileSizeHigh:      DWORD,
+	nFileSizeLow:       DWORD,
+	dwReserved0:        DWORD,
+	dwReserved1:        DWORD,
+	cFileName:          [MAX_PATH]WCHAR,
+	cAlternateFileName: [14]WCHAR,
+	_OBSOLETE_dwFileType:    DWORD, // Obsolete. Do not use.
+	_OBSOLETE_dwCreatorType: DWORD, // Obsolete. Do not use
+	_OBSOLETE_wFinderFlags:  WORD,  // Obsolete. Do not use
+}
+
+FILE_ID_128 :: struct {
+	Identifier: [16]BYTE,
+}
+
+FILE_ID_INFO :: struct {
+	VolumeSerialNumber: ULONGLONG,
+	FileId:             FILE_ID_128,
 }
 }
 
 
 CREATESTRUCTA :: struct {
 CREATESTRUCTA :: struct {
@@ -2714,41 +2726,41 @@ NEON128 :: struct {
 
 
 EXCEPTION_POINTERS :: struct {
 EXCEPTION_POINTERS :: struct {
 	ExceptionRecord: ^EXCEPTION_RECORD,
 	ExceptionRecord: ^EXCEPTION_RECORD,
-	ContextRecord: ^CONTEXT,
+	ContextRecord:   ^CONTEXT,
 }
 }
 
 
 PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG
 PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG
 
 
 CONSOLE_READCONSOLE_CONTROL :: struct {
 CONSOLE_READCONSOLE_CONTROL :: struct {
-	nLength: ULONG,
-	nInitialChars: ULONG,
-	dwCtrlWakeupMask: ULONG,
+	nLength:           ULONG,
+	nInitialChars:     ULONG,
+	dwCtrlWakeupMask:  ULONG,
 	dwControlKeyState: ULONG,
 	dwControlKeyState: ULONG,
 }
 }
 
 
 PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
 PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL
 
 
 BY_HANDLE_FILE_INFORMATION :: struct {
 BY_HANDLE_FILE_INFORMATION :: struct {
-	dwFileAttributes: DWORD,
-	ftCreationTime: FILETIME,
-	ftLastAccessTime: FILETIME,
-	ftLastWriteTime: FILETIME,
+	dwFileAttributes:     DWORD,
+	ftCreationTime:       FILETIME,
+	ftLastAccessTime:     FILETIME,
+	ftLastWriteTime:      FILETIME,
 	dwVolumeSerialNumber: DWORD,
 	dwVolumeSerialNumber: DWORD,
-	nFileSizeHigh: DWORD,
-	nFileSizeLow: DWORD,
-	nNumberOfLinks: DWORD,
-	nFileIndexHigh: DWORD,
-	nFileIndexLow: DWORD,
+	nFileSizeHigh:        DWORD,
+	nFileSizeLow:         DWORD,
+	nNumberOfLinks:       DWORD,
+	nFileIndexHigh:       DWORD,
+	nFileIndexLow:        DWORD,
 }
 }
 
 
 LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
 LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION
 
 
 FILE_STANDARD_INFO :: struct {
 FILE_STANDARD_INFO :: struct {
 	AllocationSize: LARGE_INTEGER,
 	AllocationSize: LARGE_INTEGER,
-	EndOfFile: LARGE_INTEGER,
-	NumberOfLinks: DWORD,
-	DeletePending: BOOLEAN,
-	Directory: BOOLEAN,
+	EndOfFile:      LARGE_INTEGER,
+	NumberOfLinks:  DWORD,
+	DeletePending:  BOOLEAN,
+	Directory:      BOOLEAN,
 }
 }
 
 
 FILE_ATTRIBUTE_TAG_INFO :: struct {
 FILE_ATTRIBUTE_TAG_INFO :: struct {

+ 45 - 1
core/testing/runner.odin

@@ -6,6 +6,7 @@ import "base:runtime"
 import "core:bytes"
 import "core:bytes"
 import "core:encoding/ansi"
 import "core:encoding/ansi"
 @require import "core:encoding/base64"
 @require import "core:encoding/base64"
+@require import "core:encoding/json"
 import "core:fmt"
 import "core:fmt"
 import "core:io"
 import "core:io"
 @require import pkg_log "core:log"
 @require import pkg_log "core:log"
@@ -44,7 +45,8 @@ SHARED_RANDOM_SEED    : u64    : #config(ODIN_TEST_RANDOM_SEED, 0)
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, "info")
 LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, "info")
 // Show only the most necessary logging information.
 // Show only the most necessary logging information.
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
 USING_SHORT_LOGS      : bool   : #config(ODIN_TEST_SHORT_LOGS, false)
-
+// Output a report of the tests to the given path.
+JSON_REPORT           : string : #config(ODIN_TEST_JSON_REPORT, "")
 
 
 get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 	when ODIN_DEBUG {
 	when ODIN_DEBUG {
@@ -61,6 +63,18 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
 	}
 	}
 }
 }
 
 
+JSON :: struct {
+	total:    int,
+	success:  int,
+	duration: time.Duration,
+	packages: map[string][dynamic]JSON_Test,
+}
+
+JSON_Test :: struct {
+	success: bool,
+	name:    string,
+}
+
 end_t :: proc(t: ^T) {
 end_t :: proc(t: ^T) {
 	for i := len(t.cleanups)-1; i >= 0; i -= 1 {
 	for i := len(t.cleanups)-1; i >= 0; i -= 1 {
 		#no_bounds_check c := t.cleanups[i]
 		#no_bounds_check c := t.cleanups[i]
@@ -847,5 +861,35 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
 
 
 	fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
 	fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
 
 
+	when JSON_REPORT != "" {
+		json_report: JSON
+
+		mode: int
+		when ODIN_OS != .Windows {
+			mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH
+		}
+		json_fd, errno := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
+		fmt.assertf(errno == os.ERROR_NONE, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, errno)
+		defer os.close(json_fd)
+
+		for test, i in report.all_tests {
+			#no_bounds_check state := report.all_test_states[i]
+
+			if test.pkg not_in json_report.packages {
+				json_report.packages[test.pkg] = {}
+			}
+
+			tests := &json_report.packages[test.pkg]
+			append(tests, JSON_Test{name = test.name, success = state == .Successful})
+		}
+
+		json_report.total    = len(internal_tests)
+		json_report.success  = total_success_count
+		json_report.duration = finished_in
+
+		err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true })
+		fmt.assertf(err == nil, "Error writing JSON report: %v", err)
+	}
+
 	return total_success_count == total_test_count
 	return total_success_count == total_test_count
 }
 }

+ 1 - 1
src/llvm_backend_opt.cpp

@@ -396,7 +396,7 @@ gb_internal LLVMValueRef lb_run_instrumentation_pass_insert_call(lbProcedure *p,
 	lbValue cc = lb_find_procedure_value_from_entity(m, entity);
 	lbValue cc = lb_find_procedure_value_from_entity(m, entity);
 
 
 	LLVMValueRef args[3] = {};
 	LLVMValueRef args[3] = {};
-	args[0] = p->value;
+	args[0] = LLVMConstPointerCast(p->value, lb_type(m, t_rawptr));
 
 
 	if (is_arch_wasm()) {
 	if (is_arch_wasm()) {
 		args[1] = LLVMConstPointerNull(lb_type(m, t_rawptr));
 		args[1] = LLVMConstPointerNull(lb_type(m, t_rawptr));