ソースを参照

Merge pull request #4675 from jasonKercher/os2-dir-linux

Implement os2 dir linux
gingerBill 7 ヶ月 前
コミット
48a7ed01f8
2 ファイル変更96 行追加19 行削除
  1. 84 3
      core/os/os2/dir_linux.odin
  2. 12 16
      core/sys/linux/wrappers.odin

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

@@ -1,20 +1,101 @@
 #+private
 package os2
 
-Read_Directory_Iterator_Impl :: struct {
+import "core:sys/linux"
 
+Read_Directory_Iterator_Impl :: struct {
+	prev_fi:        File_Info,
+	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) {
+		for d in linux.dirent_iterate_buf(entries, offset) {
+			file_name = linux.dirent_name(d)
+			if file_name == "." || file_name == ".." {
+				continue
+			}
+
+			file_name_cstr := cstring(raw_data(file_name))
+			entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH})
+			if errno == .NONE {
+				return entry_fd, file_name
+			}
+		}
+		return -1, ""
+	}
+
+	index = it.impl.index
+	it.impl.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)
+
+	for entry_fd == -1 {
+		if len(it.impl.dirent_backing) == 0 {
+			it.impl.dirent_backing = make([]u8, 512, file_allocator())
+		}
+
+		loop: for {
+			buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:])
+			#partial switch errno {
+			case .EINVAL:
+				delete(it.impl.dirent_backing, file_allocator())
+				n := len(it.impl.dirent_backing) * 2
+				it.impl.dirent_backing = make([]u8, n, file_allocator())
+				continue
+			case .NONE:
+				if buflen == 0 {
+					return
+				}
+				it.impl.dirent_off = 0
+				it.impl.dirent_buflen = buflen
+				entries = it.impl.dirent_backing[:buflen]
+				break loop
+			case: // error
+				return
+			}
+		}
+
+		entry_fd, file_name = scan_entries(dfd, entries, &it.impl.dirent_off)
+	}
+	defer linux.close(entry_fd)
+
+	file_info_delete(it.impl.prev_fi, file_allocator())
+	fi, _ = _fstat_internal(entry_fd, file_allocator())
+	it.impl.prev_fi = fi
+
+	ok = true
 	return
 }
 
 @(require_results)
 _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) {
-	return {}, .Unsupported
+	if f == nil || f.impl == nil {
+		return {}, .Invalid_File
+	}
+
+	stat: linux.Stat
+	errno := linux.fstat(linux.Fd(fd(f)), &stat)
+	if errno != .NONE {
+		return {}, _get_platform_error(errno)
+	}
+	if (stat.mode & linux.S_IFMT) != linux.S_IFDIR {
+		return {}, .Invalid_Dir
+	}
+	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())
 }

+ 12 - 16
core/sys/linux/wrappers.odin

@@ -86,22 +86,18 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent,
 /// The lifetime of the string is bound to the lifetime of the provided dirent structure
 dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
 	str := ([^]u8)(&dirent.name)
-	// Note(flysand): The string size calculated above applies only to the ideal case
-	// we subtract 1 byte from the string size, because a null terminator is guaranteed
-	// to be present. But! That said, the dirents are aligned to 8 bytes and the padding
-	// between the null terminator and the start of the next struct may be not initialized
-	// which means we also have to scan these garbage bytes.
-	str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name)
-	// This skips *only* over the garbage, since if we're not garbage we're at nul terminator,
-	// which skips this loop
-	for str[str_size] != 0 {
-		str_size -= 1
-	}
-	for str[str_size-1] == 0 {
-		str_size -= 1
+	// Dirents are aligned to 8 bytes, so there is guaranteed to be a null
+	// terminator in the last 8 bytes.
+	str_size := int(dirent.reclen) - cast(int)offset_of(Dirent, name)
+
+	trunc := min(str_size, 8)
+	str_size -= trunc
+	for _ in 0..<trunc {
+		str_size += 1
+		if str[str_size] == 0 {
+			break
+		}
 	}
-	// Oh yeah btw i could also just `repne scasb` this thing, but honestly I started doing
-	// it the painful way, might as well finish doing it that way
 	return string(str[:str_size])
 }
 
@@ -117,4 +113,4 @@ perf_cache_config :: #force_inline proc "contextless" (id: Perf_Hardware_Cache_I
 	op: Perf_Hardware_Cache_Op_Id,
 	res: Perf_Hardware_Cache_Result_Id) -> u64 {
 	return u64(id) | (u64(op) << 8) | (u64(res) << 16)
-}
+}