mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
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:
+118
-6
@@ -20,7 +20,7 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
|
|||||||
|
|
||||||
TEMP_ALLOCATOR_GUARD()
|
TEMP_ALLOCATOR_GUARD()
|
||||||
|
|
||||||
it := read_directory_iterator_create(f) or_return
|
it := read_directory_iterator_create(f)
|
||||||
defer _read_directory_iterator_destroy(&it)
|
defer _read_directory_iterator_destroy(&it)
|
||||||
|
|
||||||
dfi := make([dynamic]File_Info, 0, size, temp_allocator())
|
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 {
|
if n > 0 && index == n {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = read_directory_iterator_error(&it) or_break
|
||||||
|
|
||||||
append(&dfi, file_info_clone(fi, allocator) or_return)
|
append(&dfi, file_info_clone(fi, allocator) or_return)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = read_directory_iterator_error(&it) or_return
|
||||||
|
|
||||||
return slice.clone(dfi[:], allocator)
|
return slice.clone(dfi[:], allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,22 +66,129 @@ read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -
|
|||||||
|
|
||||||
|
|
||||||
Read_Directory_Iterator :: struct {
|
Read_Directory_Iterator :: struct {
|
||||||
f: ^File,
|
f: ^File,
|
||||||
|
err: struct {
|
||||||
|
err: Error,
|
||||||
|
path: [dynamic]byte,
|
||||||
|
},
|
||||||
|
index: int,
|
||||||
impl: Read_Directory_Iterator_Impl,
|
impl: Read_Directory_Iterator_Impl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a directory iterator with the given directory.
|
||||||
|
|
||||||
@(require_results)
|
For an example on how to use the iterator, see `read_directory_iterator`.
|
||||||
read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
|
*/
|
||||||
return _read_directory_iterator_create(f)
|
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) {
|
read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||||
|
if it == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(it.err.path)
|
||||||
|
|
||||||
_read_directory_iterator_destroy(it)
|
_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)
|
@(require_results)
|
||||||
read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
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)
|
return _read_directory_iterator(it)
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-14
@@ -8,12 +8,11 @@ Read_Directory_Iterator_Impl :: struct {
|
|||||||
dirent_backing: []u8,
|
dirent_backing: []u8,
|
||||||
dirent_buflen: int,
|
dirent_buflen: int,
|
||||||
dirent_off: int,
|
dirent_off: int,
|
||||||
index: int,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
_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) {
|
for d in linux.dirent_iterate_buf(entries, offset) {
|
||||||
file_name = linux.dirent_name(d)
|
file_name = linux.dirent_name(d)
|
||||||
if file_name == "." || file_name == ".." {
|
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})
|
entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
|
||||||
if errno == .NONE {
|
if errno == .NONE {
|
||||||
return entry_fd, file_name
|
return entry_fd, file_name
|
||||||
|
} else {
|
||||||
|
read_directory_iterator_set_error(it, file_name, _get_platform_error(errno))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1, ""
|
return -1, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
index = it.impl.index
|
index = it.index
|
||||||
it.impl.index += 1
|
it.index += 1
|
||||||
|
|
||||||
dfd := linux.Fd(_fd(it.f))
|
dfd := linux.Fd(_fd(it.f))
|
||||||
|
|
||||||
entries := it.impl.dirent_backing[:it.impl.dirent_buflen]
|
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 {
|
for entry_fd == -1 {
|
||||||
if len(it.impl.dirent_backing) == 0 {
|
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
|
it.impl.dirent_buflen = buflen
|
||||||
entries = it.impl.dirent_backing[:buflen]
|
entries = it.impl.dirent_backing[:buflen]
|
||||||
break loop
|
break loop
|
||||||
case: // error
|
case:
|
||||||
|
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
|
||||||
return
|
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)
|
defer linux.close(entry_fd)
|
||||||
|
|
||||||
|
// PERF: reuse the fullpath string like on posix and wasi.
|
||||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
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
|
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
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||||
_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
|
// 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 {
|
if f == nil || f.impl == nil {
|
||||||
return {}, .Invalid_File
|
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stat: linux.Stat
|
stat: linux.Stat
|
||||||
errno := linux.fstat(linux.Fd(fd(f)), &stat)
|
errno := linux.fstat(linux.Fd(fd(f)), &stat)
|
||||||
if errno != .NONE {
|
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 {
|
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) {
|
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||||
if it == nil {
|
if it == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(it.impl.dirent_backing, file_allocator())
|
delete(it.impl.dirent_backing, file_allocator())
|
||||||
file_info_delete(it.impl.prev_fi, file_allocator())
|
file_info_delete(it.impl.prev_fi, file_allocator())
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-28
@@ -6,7 +6,6 @@ import "core:sys/posix"
|
|||||||
|
|
||||||
Read_Directory_Iterator_Impl :: struct {
|
Read_Directory_Iterator_Impl :: struct {
|
||||||
dir: posix.DIR,
|
dir: posix.DIR,
|
||||||
idx: int,
|
|
||||||
fullpath: [dynamic]byte,
|
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) {
|
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||||
fimpl := (^File_Impl)(it.f.impl)
|
fimpl := (^File_Impl)(it.f.impl)
|
||||||
|
|
||||||
index = it.impl.idx
|
index = it.index
|
||||||
it.impl.idx += 1
|
it.index += 1
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
posix.set_errno(nil)
|
||||||
entry := posix.readdir(it.impl.dir)
|
entry := posix.readdir(it.impl.dir)
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
// NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator`
|
if errno := posix.errno(); errno != nil {
|
||||||
// There isn't a way to now know if it failed or if we are at the end.
|
read_directory_iterator_set_error(it, name(it.f), _get_platform_error(errno))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,54 +32,62 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
|||||||
}
|
}
|
||||||
sname := string(cname)
|
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
|
n := len(fimpl.name)+1
|
||||||
if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
copy(it.impl.fullpath[n:], sname)
|
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[:]))
|
fi = internal_stat(stat, string(it.impl.fullpath[:]))
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||||
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
|
|
||||||
if f == nil || f.impl == nil {
|
if f == nil || f.impl == nil {
|
||||||
err = .Invalid_File
|
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
impl := (^File_Impl)(f.impl)
|
impl := (^File_Impl)(f.impl)
|
||||||
|
|
||||||
iter.f = f
|
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||||
iter.impl.idx = 0
|
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(&it.impl.fullpath, impl.name)
|
||||||
append(&iter.impl.fullpath, impl.name)
|
append(&it.impl.fullpath, "/")
|
||||||
append(&iter.impl.fullpath, "/")
|
|
||||||
defer if err != nil { delete(iter.impl.fullpath) }
|
|
||||||
|
|
||||||
// `fdopendir` consumes the file descriptor so we need to `dup` it.
|
// `fdopendir` consumes the file descriptor so we need to `dup` it.
|
||||||
dupfd := posix.dup(impl.fd)
|
dupfd := posix.dup(impl.fd)
|
||||||
if dupfd == -1 {
|
if dupfd == -1 {
|
||||||
err = _get_platform_error()
|
read_directory_iterator_set_error(it, name(f), _get_platform_error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer if err != nil { posix.close(dupfd) }
|
defer if it.err.err != nil { posix.close(dupfd) }
|
||||||
|
|
||||||
iter.impl.dir = posix.fdopendir(dupfd)
|
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||||
if iter.impl.dir == nil {
|
if it.impl.dir != nil {
|
||||||
err = _get_platform_error()
|
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
|
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) {
|
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||||
if it == nil || it.impl.dir == nil {
|
if it.impl.dir == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+37
-23
@@ -1,6 +1,8 @@
|
|||||||
#+private
|
#+private
|
||||||
package os2
|
package os2
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
|
import "core:slice"
|
||||||
import "base:intrinsics"
|
import "base:intrinsics"
|
||||||
import "core:sys/wasm/wasi"
|
import "core:sys/wasm/wasi"
|
||||||
|
|
||||||
@@ -8,7 +10,6 @@ Read_Directory_Iterator_Impl :: struct {
|
|||||||
fullpath: [dynamic]byte,
|
fullpath: [dynamic]byte,
|
||||||
buf: []byte,
|
buf: []byte,
|
||||||
off: int,
|
off: int,
|
||||||
idx: int,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
@@ -17,8 +18,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
|||||||
|
|
||||||
buf := it.impl.buf[it.impl.off:]
|
buf := it.impl.buf[it.impl.off:]
|
||||||
|
|
||||||
index = it.impl.idx
|
index = it.index
|
||||||
it.impl.idx += 1
|
it.index += 1
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if len(buf) < size_of(wasi.dirent_t) {
|
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)))
|
entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf)))
|
||||||
buf = buf[size_of(wasi.dirent_t):]
|
buf = buf[size_of(wasi.dirent_t):]
|
||||||
|
|
||||||
if len(buf) < int(entry.d_namlen) {
|
assert(len(buf) < int(entry.d_namlen))
|
||||||
// shouldn't be possible.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
name := string(buf[:entry.d_namlen])
|
name := string(buf[:entry.d_namlen])
|
||||||
buf = 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
|
n := len(fimpl.name)+1
|
||||||
if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
copy(it.impl.fullpath[n:], name)
|
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,
|
ino = entry.d_ino,
|
||||||
filetype = entry.d_type,
|
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[:]))
|
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_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||||
_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) {
|
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||||
|
it.impl.off = 0
|
||||||
|
|
||||||
if f == nil || f.impl == nil {
|
if f == nil || f.impl == nil {
|
||||||
err = .Invalid_File
|
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
impl := (^File_Impl)(f.impl)
|
impl := (^File_Impl)(f.impl)
|
||||||
iter.f = f
|
|
||||||
|
|
||||||
buf: [dynamic]byte
|
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()
|
buf.allocator = file_allocator()
|
||||||
defer if err != nil { delete(buf) }
|
|
||||||
|
|
||||||
// NOTE: this is very grug.
|
defer if it.err.err != nil { delete(buf) }
|
||||||
|
|
||||||
for {
|
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)
|
n, err := wasi.fd_readdir(__fd(f), buf[:], 0)
|
||||||
if _err != nil {
|
if err != nil {
|
||||||
err = _get_platform_error(_err)
|
read_directory_iterator_set_error(it, name(f), _get_platform_error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +102,18 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera
|
|||||||
|
|
||||||
assert(n == len(buf))
|
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
|
// NOTE: Allow calling `init` to target a new directory with the same iterator.
|
||||||
append(&iter.impl.fullpath, impl.name)
|
it.impl.fullpath.allocator = file_allocator()
|
||||||
append(&iter.impl.fullpath, "/")
|
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
|
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) {
|
_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) {
|
||||||
delete(it.impl.buf, file_allocator())
|
delete(it.impl.buf, file_allocator())
|
||||||
delete(it.impl.fullpath)
|
delete(it.impl.fullpath)
|
||||||
it^ = {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,16 +44,11 @@ Read_Directory_Iterator_Impl :: struct {
|
|||||||
path: string,
|
path: string,
|
||||||
prev_fi: File_Info,
|
prev_fi: File_Info,
|
||||||
no_more_files: bool,
|
no_more_files: bool,
|
||||||
index: int,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
|
||||||
if it.f == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TEMP_ALLOCATOR_GUARD()
|
TEMP_ALLOCATOR_GUARD()
|
||||||
|
|
||||||
for !it.impl.no_more_files {
|
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())
|
fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
read_directory_iterator_set_error(it, it.impl.path, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.name != "" {
|
if fi.name != "" {
|
||||||
it.impl.prev_fi = fi
|
it.impl.prev_fi = fi
|
||||||
ok = true
|
ok = true
|
||||||
index = it.impl.index
|
index = it.index
|
||||||
it.impl.index += 1
|
it.index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
|
if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) {
|
||||||
e := _get_platform_error()
|
e := _get_platform_error()
|
||||||
if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) {
|
if pe, _ := is_platform_error(e); pe != i32(win32.ERROR_NO_MORE_FILES) {
|
||||||
it.impl.no_more_files = true
|
read_directory_iterator_set_error(it, it.impl.path, e)
|
||||||
}
|
}
|
||||||
it.impl.no_more_files = true
|
it.impl.no_more_files = true
|
||||||
}
|
}
|
||||||
@@ -86,16 +83,27 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@(require_results)
|
_read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
|
||||||
_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) {
|
it.impl.no_more_files = false
|
||||||
if f == nil {
|
|
||||||
|
if f == nil || f.impl == nil {
|
||||||
|
read_directory_iterator_set_error(it, "", .Invalid_File)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
it.f = f
|
it.f = f
|
||||||
impl := (^File_Impl)(f.impl)
|
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) {
|
if !is_directory(impl.name) {
|
||||||
err = .Invalid_Dir
|
read_directory_iterator_set_error(it, impl.name, .Invalid_Dir)
|
||||||
return
|
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)
|
it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data)
|
||||||
if it.impl.find_handle == win32.INVALID_HANDLE_VALUE {
|
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
|
return
|
||||||
}
|
}
|
||||||
defer if err != nil {
|
defer if it.err.err != nil {
|
||||||
win32.FindClose(it.impl.find_handle)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,11 @@ new_file :: proc(handle: uintptr, name: string) -> ^File {
|
|||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
clone :: proc(f: ^File) -> (^File, Error) {
|
||||||
|
return _clone(f)
|
||||||
|
}
|
||||||
|
|
||||||
@(require_results)
|
@(require_results)
|
||||||
fd :: proc(f: ^File) -> uintptr {
|
fd :: proc(f: ^File) -> uintptr {
|
||||||
return _fd(f)
|
return _fd(f)
|
||||||
|
|||||||
@@ -113,6 +113,23 @@ _new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^
|
|||||||
return &impl.file, nil
|
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)
|
@(require_results)
|
||||||
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
|
_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) {
|
||||||
|
|||||||
@@ -114,6 +114,29 @@ __new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File {
|
|||||||
return &impl.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) {
|
_close :: proc(f: ^File_Impl) -> (err: Error) {
|
||||||
if f == nil { return nil }
|
if f == nil { return nil }
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,32 @@ _new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -
|
|||||||
return &impl.file, nil
|
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) {
|
_close :: proc(f: ^File_Impl) -> (err: Error) {
|
||||||
if errno := wasi.fd_close(f.fd); errno != nil {
|
if errno := wasi.fd_close(f.fd); errno != nil {
|
||||||
err = _get_platform_error(errno)
|
err = _get_platform_error(errno)
|
||||||
|
|||||||
@@ -210,6 +210,29 @@ _new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) ->
|
|||||||
return
|
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 {
|
_fd :: proc(f: ^File) -> uintptr {
|
||||||
if f == nil || f.impl == nil {
|
if f == nil || f.impl == nil {
|
||||||
|
|||||||
@@ -60,16 +60,20 @@ _remove_all :: proc(path: string) -> (err: Error) {
|
|||||||
dir := open(path) or_return
|
dir := open(path) or_return
|
||||||
defer close(dir)
|
defer close(dir)
|
||||||
|
|
||||||
iter := read_directory_iterator_create(dir) or_return
|
iter := read_directory_iterator_create(dir)
|
||||||
defer read_directory_iterator_destroy(&iter)
|
defer read_directory_iterator_destroy(&iter)
|
||||||
|
|
||||||
for fi in read_directory_iterator(&iter) {
|
for fi in read_directory_iterator(&iter) {
|
||||||
|
_ = read_directory_iterator_error(&iter) or_break
|
||||||
|
|
||||||
if fi.type == .Directory {
|
if fi.type == .Directory {
|
||||||
_remove_all(fi.fullpath) or_return
|
_remove_all(fi.fullpath) or_return
|
||||||
} else {
|
} else {
|
||||||
remove(fi.fullpath) or_return
|
remove(fi.fullpath) or_return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = read_directory_iterator_error(&iter) or_return
|
||||||
}
|
}
|
||||||
|
|
||||||
return remove(path)
|
return remove(path)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import "core:log"
|
|||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_read_dir :: proc(t: ^testing.T) {
|
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].name, "sub")
|
||||||
testing.expect_value(t, fis[1].type, os.File_Type.Directory)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user