Merge pull request #4877 from laytan/os2-additions

os/os2: recursive directory walker, expose errors in read_directory, file clone
This commit is contained in:
gingerBill
2025-02-28 14:37:47 +00:00
committed by GitHub
14 changed files with 687 additions and 88 deletions

View File

@@ -20,7 +20,7 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
TEMP_ALLOCATOR_GUARD()
it := read_directory_iterator_create(f) or_return
it := read_directory_iterator_create(f)
defer _read_directory_iterator_destroy(&it)
dfi := make([dynamic]File_Info, 0, size, temp_allocator())
@@ -34,9 +34,14 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
if n > 0 && index == n {
break
}
_ = read_directory_iterator_error(&it) or_break
append(&dfi, file_info_clone(fi, allocator) or_return)
}
_ = read_directory_iterator_error(&it) or_return
return slice.clone(dfi[:], allocator)
}
@@ -61,22 +66,129 @@ read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -
Read_Directory_Iterator :: struct {
f: ^File,
f: ^File,
err: struct {
err: Error,
path: [dynamic]byte,
},
index: int,
impl: Read_Directory_Iterator_Impl,
}
/*
Creates a directory iterator with the given directory.
@(require_results)
read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
return _read_directory_iterator_create(f)
For an example on how to use the iterator, see `read_directory_iterator`.
*/
read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator) {
read_directory_iterator_init(&it, f)
return
}
/*
Initialize a directory iterator with the given directory.
This procedure may be called on an existing iterator to reuse it for another directory.
For an example on how to use the iterator, see `read_directory_iterator`.
*/
read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
it.err.err = nil
it.err.path.allocator = file_allocator()
clear(&it.err.path)
it.f = f
it.index = 0
_read_directory_iterator_init(it, f)
}
/*
Destroys a directory iterator.
*/
read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it == nil {
return
}
delete(it.err.path)
_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`
/*
Retrieve the last error that happened during iteration.
*/
@(require_results)
read_directory_iterator_error :: proc(it: ^Read_Directory_Iterator) -> (path: string, err: Error) {
return string(it.err.path[:]), it.err.err
}
@(private)
read_directory_iterator_set_error :: proc(it: ^Read_Directory_Iterator, path: string, err: Error) {
if err == nil {
return
}
resize(&it.err.path, len(path))
copy(it.err.path[:], path)
it.err.err = err
}
/*
Returns the next file info entry for the iterator's directory.
The given `File_Info` is reused in subsequent calls so a copy (`file_info_clone`) has to be made to
extend its lifetime.
Example:
package main
import "core:fmt"
import os "core:os/os2"
main :: proc() {
f, oerr := os.open("core")
ensure(oerr == nil)
defer os.close(f)
it := os.read_directory_iterator_create(f)
defer os.read_directory_iterator_destroy(&it)
for info in os.read_directory_iterator(&it) {
// Optionally break on the first error:
// Supports not doing this, and keeping it going with remaining items.
// _ = os.read_directory_iterator_error(&it) or_break
// Handle error as we go:
// Again, no need to do this as it will keep going with remaining items.
if path, err := os.read_directory_iterator_error(&it); err != nil {
fmt.eprintfln("failed reading %s: %s", path, err)
continue
}
// Or, do not handle errors during iteration, and just check the error at the end.
fmt.printfln("%#v", info)
}
// Handle error if one happened during iteration at the end:
if path, err := os.read_directory_iterator_error(&it); err != nil {
fmt.eprintfln("read directory failed at %s: %s", path, err)
}
}
*/
@(require_results)
read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
if it.f == nil {
return
}
if it.index == 0 && it.err.err != nil {
return
}
return _read_directory_iterator(it)
}

View File

@@ -8,12 +8,11 @@ Read_Directory_Iterator_Impl :: struct {
dirent_backing: []u8,
dirent_buflen: int,
dirent_off: int,
index: int,
}
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
scan_entries :: proc(dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) {
scan_entries :: proc(it: ^Read_Directory_Iterator, dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) {
for d in linux.dirent_iterate_buf(entries, offset) {
file_name = linux.dirent_name(d)
if file_name == "." || file_name == ".." {
@@ -24,18 +23,21 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
if errno == .NONE {
return entry_fd, file_name
} else {
read_directory_iterator_set_error(it, file_name, _get_platform_error(errno))
}
}
return -1, ""
}
index = it.impl.index
it.impl.index += 1
index = it.index
it.index += 1
dfd := linux.Fd(_fd(it.f))
entries := it.impl.dirent_backing[:it.impl.dirent_buflen]
entry_fd, file_name := scan_entries(dfd, entries, &it.impl.dirent_off)
entry_fd, file_name := scan_entries(it, dfd, entries, &it.impl.dirent_off)
for entry_fd == -1 {
if len(it.impl.dirent_backing) == 0 {
@@ -58,44 +60,60 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
it.impl.dirent_buflen = buflen
entries = it.impl.dirent_backing[:buflen]
break loop
case: // error
case:
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
return
}
}
entry_fd, file_name = scan_entries(dfd, entries, &it.impl.dirent_off)
entry_fd, file_name = scan_entries(it, dfd, entries, &it.impl.dirent_off)
}
defer linux.close(entry_fd)
// PERF: reuse the fullpath string like on posix and wasi.
file_info_delete(it.impl.prev_fi, file_allocator())
fi, _ = _fstat_internal(entry_fd, file_allocator())
err: Error
fi, err = _fstat_internal(entry_fd, file_allocator())
it.impl.prev_fi = fi
if err != nil {
path, _ := _get_full_path(entry_fd, temp_allocator())
read_directory_iterator_set_error(it, path, err)
}
ok = true
return
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
// NOTE: Allow calling `init` to target a new directory with the same iterator.
it.impl.dirent_buflen = 0
it.impl.dirent_off = 0
if f == nil || f.impl == nil {
return {}, .Invalid_File
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
stat: linux.Stat
errno := linux.fstat(linux.Fd(fd(f)), &stat)
if errno != .NONE {
return {}, _get_platform_error(errno)
read_directory_iterator_set_error(it, name(f), _get_platform_error(errno))
return
}
if (stat.mode & linux.S_IFMT) != linux.S_IFDIR {
return {}, .Invalid_Dir
read_directory_iterator_set_error(it, name(f), .Invalid_Dir)
return
}
return {f = f}, nil
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it == nil {
return
}
delete(it.impl.dirent_backing, file_allocator())
file_info_delete(it.impl.prev_fi, file_allocator())
}

View File

@@ -6,7 +6,6 @@ import "core:sys/posix"
Read_Directory_Iterator_Impl :: struct {
dir: posix.DIR,
idx: int,
fullpath: [dynamic]byte,
}
@@ -14,14 +13,16 @@ Read_Directory_Iterator_Impl :: struct {
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
fimpl := (^File_Impl)(it.f.impl)
index = it.impl.idx
it.impl.idx += 1
index = it.index
it.index += 1
for {
posix.set_errno(nil)
entry := posix.readdir(it.impl.dir)
if entry == nil {
// NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator`
// There isn't a way to now know if it failed or if we are at the end.
if errno := posix.errno(); errno != nil {
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
}
return
}
@@ -31,54 +32,62 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
}
sname := string(cname)
stat: posix.stat_t
if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK {
// NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator`
// There isn't a way to now know if it failed or if we are at the end.
return
}
n := len(fimpl.name)+1
if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil {
// Can't really tell caller we had an error, sad.
read_directory_iterator_set_error(it, sname, err)
ok = true
return
}
copy(it.impl.fullpath[n:], sname)
stat: posix.stat_t
if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK {
read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error())
ok = true
return
}
fi = internal_stat(stat, string(it.impl.fullpath[:]))
ok = true
return
}
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
if f == nil || f.impl == nil {
err = .Invalid_File
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
impl := (^File_Impl)(f.impl)
iter.f = f
iter.impl.idx = 0
// NOTE: Allow calling `init` to target a new directory with the same iterator.
it.impl.fullpath.allocator = file_allocator()
clear(&it.impl.fullpath)
if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil {
read_directory_iterator_set_error(it, name(f), err)
return
}
iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return
append(&iter.impl.fullpath, impl.name)
append(&iter.impl.fullpath, "/")
defer if err != nil { delete(iter.impl.fullpath) }
append(&it.impl.fullpath, impl.name)
append(&it.impl.fullpath, "/")
// `fdopendir` consumes the file descriptor so we need to `dup` it.
dupfd := posix.dup(impl.fd)
if dupfd == -1 {
err = _get_platform_error()
read_directory_iterator_set_error(it, name(f), _get_platform_error())
return
}
defer if err != nil { posix.close(dupfd) }
defer if it.err.err != nil { posix.close(dupfd) }
iter.impl.dir = posix.fdopendir(dupfd)
if iter.impl.dir == nil {
err = _get_platform_error()
// NOTE: Allow calling `init` to target a new directory with the same iterator.
if it.impl.dir != nil {
posix.closedir(it.impl.dir)
}
it.impl.dir = posix.fdopendir(dupfd)
if it.impl.dir == nil {
read_directory_iterator_set_error(it, name(f), _get_platform_error())
return
}
@@ -86,7 +95,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
}
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
if it == nil || it.impl.dir == nil {
if it.impl.dir == nil {
return
}

230
core/os/os2/dir_walker.odin Normal file
View File

@@ -0,0 +1,230 @@
package os2
import "core:container/queue"
/*
A recursive directory walker.
Note that none of the fields should be accessed directly.
*/
Walker :: struct {
todo: queue.Queue(string),
skip_dir: bool,
err: struct {
path: [dynamic]byte,
err: Error,
},
iter: Read_Directory_Iterator,
}
walker_init_path :: proc(w: ^Walker, path: string) {
cloned_path, err := clone_string(path, file_allocator())
if err != nil {
walker_set_error(w, path, err)
return
}
walker_clear(w)
if _, err = queue.push(&w.todo, cloned_path); err != nil {
walker_set_error(w, cloned_path, err)
return
}
}
walker_init_file :: proc(w: ^Walker, f: ^File) {
handle, err := clone(f)
if err != nil {
path, _ := clone_string(name(f), file_allocator())
walker_set_error(w, path, err)
return
}
walker_clear(w)
read_directory_iterator_init(&w.iter, handle)
}
/*
Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
You are allowed to repeatedly call this to reuse it for later walks.
For an example on how to use the walker, see `walker_walk`.
*/
walker_init :: proc {
walker_init_path,
walker_init_file,
}
@(require_results)
walker_create_path :: proc(path: string) -> (w: Walker) {
walker_init_path(&w, path)
return
}
@(require_results)
walker_create_file :: proc(f: ^File) -> (w: Walker) {
walker_init_file(&w, f)
return
}
/*
Creates a walker, either using a path or a file pointer to a directory the walker will start at.
For an example on how to use the walker, see `walker_walk`.
*/
walker_create :: proc {
walker_create_path,
walker_create_file,
}
/*
Returns the last error that occurred during the walker's operations.
Can be called while iterating, or only at the end to check if anything failed.
*/
@(require_results)
walker_error :: proc(w: ^Walker) -> (path: string, err: Error) {
return string(w.err.path[:]), w.err.err
}
@(private)
walker_set_error :: proc(w: ^Walker, path: string, err: Error) {
if err == nil {
return
}
resize(&w.err.path, len(path))
copy(w.err.path[:], path)
w.err.err = err
}
@(private)
walker_clear :: proc(w: ^Walker) {
w.iter.f = nil
w.skip_dir = false
w.err.path.allocator = file_allocator()
clear(&w.err.path)
w.todo.data.allocator = file_allocator()
for path in queue.pop_front_safe(&w.todo) {
delete(path, file_allocator())
}
}
walker_destroy :: proc(w: ^Walker) {
walker_clear(w)
queue.destroy(&w.todo)
delete(w.err.path)
read_directory_iterator_destroy(&w.iter)
}
// Marks the current directory to be skipped (not entered into).
walker_skip_dir :: proc(w: ^Walker) {
w.skip_dir = true
}
/*
Returns the next file info in the iterator, files are iterated in breadth-first order.
If an error occurred opening a directory, you may get zero'd info struct and
`walker_error` will return the error.
Example:
package main
import "core:fmt"
import "core:strings"
import os "core:os/os2"
main :: proc() {
w := os.walker_create("core")
defer os.walker_destroy(&w)
for info in os.walker_walk(&w) {
// Optionally break on the first error:
// _ = walker_error(&w) or_break
// Or, handle error as we go:
if path, err := os.walker_error(&w); err != nil {
fmt.eprintfln("failed walking %s: %s", path, err)
continue
}
// Or, do not handle errors during iteration, and just check the error at the end.
// Skip a directory:
if strings.has_suffix(info.fullpath, ".git") {
os.walker_skip_dir(&w)
continue
}
fmt.printfln("%#v", info)
}
// Handle error if one happened during iteration at the end:
if path, err := os.walker_error(&w); err != nil {
fmt.eprintfln("failed walking %s: %v", path, err)
}
}
*/
@(require_results)
walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
if w.skip_dir {
w.skip_dir = false
if skip, sok := queue.pop_back_safe(&w.todo); sok {
delete(skip, file_allocator())
}
}
if w.iter.f == nil {
if queue.len(w.todo) == 0 {
return
}
next := queue.pop_front(&w.todo)
handle, err := open(next)
if err != nil {
walker_set_error(w, next, err)
return {}, true
}
read_directory_iterator_init(&w.iter, handle)
delete(next, file_allocator())
}
info, _, iter_ok := read_directory_iterator(&w.iter)
if path, err := read_directory_iterator_error(&w.iter); err != nil {
walker_set_error(w, path, err)
}
if !iter_ok {
close(w.iter.f)
w.iter.f = nil
return walker_walk(w)
}
if info.type == .Directory {
path, err := clone_string(info.fullpath, file_allocator())
if err != nil {
walker_set_error(w, "", err)
return
}
_, err = queue.push_back(&w.todo, path)
if err != nil {
walker_set_error(w, path, err)
return
}
}
return info, iter_ok
}

View File

@@ -1,6 +1,8 @@
#+private
package os2
import "base:runtime"
import "core:slice"
import "base:intrinsics"
import "core:sys/wasm/wasi"
@@ -8,7 +10,6 @@ Read_Directory_Iterator_Impl :: struct {
fullpath: [dynamic]byte,
buf: []byte,
off: int,
idx: int,
}
@(require_results)
@@ -17,8 +18,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
buf := it.impl.buf[it.impl.off:]
index = it.impl.idx
it.impl.idx += 1
index = it.index
it.index += 1
for {
if len(buf) < size_of(wasi.dirent_t) {
@@ -28,10 +29,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf)))
buf = buf[size_of(wasi.dirent_t):]
if len(buf) < int(entry.d_namlen) {
// shouldn't be possible.
return
}
assert(len(buf) < int(entry.d_namlen))
name := string(buf[:entry.d_namlen])
buf = buf[entry.d_namlen:]
@@ -43,7 +41,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
n := len(fimpl.name)+1
if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil {
// Can't really tell caller we had an error, sad.
read_directory_iterator_set_error(it, name, alloc_err)
ok = true
return
}
copy(it.impl.fullpath[n:], name)
@@ -55,6 +54,7 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
ino = entry.d_ino,
filetype = entry.d_type,
}
read_directory_iterator_set_error(it, string(it.impl.fullpath[:]), _get_platform_error(err))
}
fi = internal_stat(stat, string(it.impl.fullpath[:]))
@@ -63,27 +63,35 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
}
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
// NOTE: Allow calling `init` to target a new directory with the same iterator.
it.impl.off = 0
if f == nil || f.impl == nil {
err = .Invalid_File
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
impl := (^File_Impl)(f.impl)
iter.f = f
buf: [dynamic]byte
// NOTE: Allow calling `init` to target a new directory with the same iterator.
if it.impl.buf != nil {
buf = slice.into_dynamic(it.impl.buf)
}
buf.allocator = file_allocator()
defer if err != nil { delete(buf) }
// NOTE: this is very grug.
defer if it.err.err != nil { delete(buf) }
for {
non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2) or_return
if err := non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2); err != nil {
read_directory_iterator_set_error(it, name(f), err)
return
}
n, _err := wasi.fd_readdir(__fd(f), buf[:], 0)
if _err != nil {
err = _get_platform_error(_err)
n, err := wasi.fd_readdir(__fd(f), buf[:], 0)
if err != nil {
read_directory_iterator_set_error(it, name(f), _get_platform_error(err))
return
}
@@ -94,11 +102,18 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
assert(n == len(buf))
}
iter.impl.buf = buf[:]
it.impl.buf = buf[:]
iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return
append(&iter.impl.fullpath, impl.name)
append(&iter.impl.fullpath, "/")
// NOTE: Allow calling `init` to target a new directory with the same iterator.
it.impl.fullpath.allocator = file_allocator()
clear(&it.impl.fullpath)
if err := reserve(&it.impl.fullpath, len(impl.name)+128); err != nil {
read_directory_iterator_set_error(it, name(f), err)
return
}
append(&it.impl.fullpath, impl.name)
append(&it.impl.fullpath, "/")
return
}
@@ -106,5 +121,4 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
delete(it.impl.buf, file_allocator())
delete(it.impl.fullpath)
it^ = {}
}

View File

@@ -44,16 +44,11 @@ Read_Directory_Iterator_Impl :: struct {
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
}
TEMP_ALLOCATOR_GUARD()
for !it.impl.no_more_files {
@@ -63,19 +58,21 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
if err != nil {
read_directory_iterator_set_error(it, it.impl.path, err)
return
}
if fi.name != "" {
it.impl.prev_fi = fi
ok = true
index = it.impl.index
it.impl.index += 1
index = it.index
it.index += 1
}
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
e := _get_platform_error()
if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
it.impl.no_more_files = true
if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) {
read_directory_iterator_set_error(it, it.impl.path, e)
}
it.impl.no_more_files = true
}
@@ -86,16 +83,27 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
return
}
@(require_results)
_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
if f == nil {
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
it.impl.no_more_files = false
if f == nil || f.impl == nil {
read_directory_iterator_set_error(it, "", .Invalid_File)
return
}
it.f = f
impl := (^File_Impl)(f.impl)
// NOTE: Allow calling `init` to target a new directory with the same iterator - reset idx.
if it.impl.find_handle != nil {
win32.FindClose(it.impl.find_handle)
}
if it.impl.path != "" {
delete(it.impl.path, file_allocator())
}
if !is_directory(impl.name) {
err = .Invalid_Dir
read_directory_iterator_set_error(it, impl.name, .Invalid_Dir)
return
}
@@ -118,14 +126,19 @@ _read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterato
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()
read_directory_iterator_set_error(it, impl.name, _get_platform_error())
return
}
defer if err != nil {
defer if it.err.err != nil {
win32.FindClose(it.impl.find_handle)
}
it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return
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
}

View File

@@ -122,6 +122,11 @@ new_file :: proc(handle: uintptr, name: string) -> ^File {
return file
}
@(require_results)
clone :: proc(f: ^File) -> (^File, Error) {
return _clone(f)
}
@(require_results)
fd :: proc(f: ^File) -> uintptr {
return _fd(f)

View File

@@ -113,6 +113,23 @@ _new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^
return &impl.file, nil
}
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
if f == nil || f.impl == nil {
return
}
fd := (^File_Impl)(f.impl).fd
clonefd, errno := linux.dup(fd)
if errno != nil {
err = _get_platform_error(errno)
return
}
defer if err != nil { linux.close(clonefd) }
return _new_file(uintptr(clonefd), "", file_allocator())
}
@(require_results)
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {

View File

@@ -114,6 +114,29 @@ __new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File {
return &impl.file
}
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
if f == nil || f.impl == nil {
err = .Invalid_Pointer
return
}
impl := (^File_Impl)(f.impl)
fd := posix.dup(impl.fd)
if fd <= 0 {
err = _get_platform_error()
return
}
defer if err != nil { posix.close(fd) }
clone = __new_file(fd, file_allocator())
clone_impl := (^File_Impl)(clone.impl)
clone_impl.cname = clone_to_cstring(impl.name, file_allocator()) or_return
clone_impl.name = string(clone_impl.cname)
return
}
_close :: proc(f: ^File_Impl) -> (err: Error) {
if f == nil { return nil }

View File

@@ -223,6 +223,32 @@ _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -
return &impl.file, nil
}
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
if f == nil || f.impl == nil {
return
}
dir_fd, relative, ok := match_preopen(name(f))
if !ok {
return nil, .Invalid_Path
}
fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, {}, {}, {}, {})
if fderr != nil {
err = _get_platform_error(fderr)
return
}
defer if err != nil { wasi.fd_close(fd) }
fderr = wasi.fd_renumber((^File_Impl)(f.impl).fd, fd)
if fderr != nil {
err = _get_platform_error(fderr)
return
}
return _new_file(uintptr(fd), name(f), file_allocator())
}
_close :: proc(f: ^File_Impl) -> (err: Error) {
if errno := wasi.fd_close(f.fd); errno != nil {
err = _get_platform_error(errno)

View File

@@ -210,6 +210,29 @@ _new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) ->
return
}
_clone :: proc(f: ^File) -> (clone: ^File, err: Error) {
if f == nil || f.impl == nil {
return
}
clonefd: win32.HANDLE
process := win32.GetCurrentProcess()
if !win32.DuplicateHandle(
process,
win32.HANDLE(_fd(f)),
process,
&clonefd,
0,
false,
win32.DUPLICATE_SAME_ACCESS,
) {
err = _get_platform_error()
return
}
defer if err != nil { win32.CloseHandle(clonefd) }
return _new_file(uintptr(clonefd), name(f), file_allocator())
}
_fd :: proc(f: ^File) -> uintptr {
if f == nil || f.impl == nil {

View File

@@ -60,16 +60,20 @@ _remove_all :: proc(path: string) -> (err: Error) {
dir := open(path) or_return
defer close(dir)
iter := read_directory_iterator_create(dir) or_return
iter := read_directory_iterator_create(dir)
defer read_directory_iterator_destroy(&iter)
for fi in read_directory_iterator(&iter) {
_ = read_directory_iterator_error(&iter) or_break
if fi.type == .Directory {
_remove_all(fi.fullpath) or_return
} else {
remove(fi.fullpath) or_return
}
}
_ = read_directory_iterator_error(&iter) or_return
}
return remove(path)

View File

@@ -5,6 +5,7 @@ import "core:log"
import "core:path/filepath"
import "core:slice"
import "core:testing"
import "core:strings"
@(test)
test_read_dir :: proc(t: ^testing.T) {
@@ -30,3 +31,76 @@ test_read_dir :: proc(t: ^testing.T) {
testing.expect_value(t, fis[1].name, "sub")
testing.expect_value(t, fis[1].type, os.File_Type.Directory)
}
@(test)
test_walker :: proc(t: ^testing.T) {
path := filepath.join({#directory, "../dir"})
defer delete(path)
w := os.walker_create(path)
defer os.walker_destroy(&w)
test_walker_internal(t, &w)
}
@(test)
test_walker_file :: proc(t: ^testing.T) {
path := filepath.join({#directory, "../dir"})
defer delete(path)
f, err := os.open(path)
testing.expect_value(t, err, nil)
defer os.close(f)
w := os.walker_create(f)
defer os.walker_destroy(&w)
test_walker_internal(t, &w)
}
test_walker_internal :: proc(t: ^testing.T, w: ^os.Walker) {
Seen :: struct {
type: os.File_Type,
path: string,
}
expected := [?]Seen{
{.Regular, filepath.join({"dir", "b.txt"})},
{.Directory, filepath.join({"dir", "sub"})},
{.Regular, filepath.join({"dir", "sub", ".gitkeep"})},
}
seen: [dynamic]Seen
defer delete(seen)
for info in os.walker_walk(w) {
errpath, err := os.walker_error(w)
testing.expectf(t, err == nil, "walker error for %q: %v", errpath, err)
append(&seen, Seen{
info.type,
strings.clone(info.fullpath),
})
}
if _, err := os.walker_error(w); err == .Unsupported {
log.warn("os2 directory functionality is unsupported, skipping test")
return
}
testing.expect_value(t, len(seen), len(expected))
for expectation in expected {
found: bool
for entry in seen {
if strings.has_suffix(entry.path, expectation.path) {
found = true
testing.expect_value(t, entry.type, expectation.type)
delete(entry.path)
}
}
testing.expectf(t, found, "%q not found in %v", expectation, seen)
delete(expectation.path)
}
}

View File

@@ -0,0 +1,31 @@
package tests_core_os_os2
import os "core:os/os2"
import "core:testing"
import "core:path/filepath"
@(test)
test_clone :: proc(t: ^testing.T) {
f, err := os.open(filepath.join({#directory, "file.odin"}, context.temp_allocator))
testing.expect_value(t, err, nil)
testing.expect(t, f != nil)
clone: ^os.File
clone, err = os.clone(f)
testing.expect_value(t, err, nil)
testing.expect(t, clone != nil)
testing.expect_value(t, os.name(clone), os.name(f))
testing.expect(t, os.fd(clone) != os.fd(f))
os.close(f)
buf: [128]byte
n: int
n, err = os.read(clone, buf[:])
testing.expect_value(t, err, nil)
testing.expect(t, n > 13)
testing.expect_value(t, string(buf[:13]), "package tests")
os.close(clone)
}