1 | // Copyright 2022 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | //go:build darwin && cgo |
6 | // +build darwin,cgo |
7 | |
8 | package fastwalk |
9 | |
10 | /* |
11 | #include <dirent.h> |
12 | |
13 | // fastwalk_readdir_r wraps readdir_r so that we don't have to pass a dirent** |
14 | // result pointer which triggers CGO's "Go pointer to Go pointer" check unless |
15 | // we allocat the result dirent* with malloc. |
16 | // |
17 | // fastwalk_readdir_r returns 0 on success, -1 upon reaching the end of the |
18 | // directory, or a positive error number to indicate failure. |
19 | static int fastwalk_readdir_r(DIR *fd, struct dirent *entry) { |
20 | struct dirent *result; |
21 | int ret = readdir_r(fd, entry, &result); |
22 | if (ret == 0 && result == NULL) { |
23 | ret = -1; // EOF |
24 | } |
25 | return ret; |
26 | } |
27 | */ |
28 | import "C" |
29 | |
30 | import ( |
31 | "os" |
32 | "syscall" |
33 | "unsafe" |
34 | ) |
35 | |
36 | func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { |
37 | fd, err := openDir(dirName) |
38 | if err != nil { |
39 | return &os.PathError{Op: "opendir", Path: dirName, Err: err} |
40 | } |
41 | defer C.closedir(fd) |
42 | |
43 | skipFiles := false |
44 | var dirent syscall.Dirent |
45 | for { |
46 | ret := int(C.fastwalk_readdir_r(fd, (*C.struct_dirent)(unsafe.Pointer(&dirent)))) |
47 | if ret != 0 { |
48 | if ret == -1 { |
49 | break // EOF |
50 | } |
51 | if ret == int(syscall.EINTR) { |
52 | continue |
53 | } |
54 | return &os.PathError{Op: "readdir", Path: dirName, Err: syscall.Errno(ret)} |
55 | } |
56 | if dirent.Ino == 0 { |
57 | continue |
58 | } |
59 | typ := dtToType(dirent.Type) |
60 | if skipFiles && typ.IsRegular() { |
61 | continue |
62 | } |
63 | name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] |
64 | name = name[:dirent.Namlen] |
65 | for i, c := range name { |
66 | if c == 0 { |
67 | name = name[:i] |
68 | break |
69 | } |
70 | } |
71 | // Check for useless names before allocating a string. |
72 | if string(name) == "." || string(name) == ".." { |
73 | continue |
74 | } |
75 | if err := fn(dirName, string(name), typ); err != nil { |
76 | if err != ErrSkipFiles { |
77 | return err |
78 | } |
79 | skipFiles = true |
80 | } |
81 | } |
82 | |
83 | return nil |
84 | } |
85 | |
86 | func dtToType(typ uint8) os.FileMode { |
87 | switch typ { |
88 | case syscall.DT_BLK: |
89 | return os.ModeDevice |
90 | case syscall.DT_CHR: |
91 | return os.ModeDevice | os.ModeCharDevice |
92 | case syscall.DT_DIR: |
93 | return os.ModeDir |
94 | case syscall.DT_FIFO: |
95 | return os.ModeNamedPipe |
96 | case syscall.DT_LNK: |
97 | return os.ModeSymlink |
98 | case syscall.DT_REG: |
99 | return 0 |
100 | case syscall.DT_SOCK: |
101 | return os.ModeSocket |
102 | } |
103 | return ^os.FileMode(0) |
104 | } |
105 | |
106 | // openDir wraps opendir(3) and handles any EINTR errors. The returned *DIR |
107 | // needs to be closed with closedir(3). |
108 | func openDir(path string) (*C.DIR, error) { |
109 | name, err := syscall.BytePtrFromString(path) |
110 | if err != nil { |
111 | return nil, err |
112 | } |
113 | for { |
114 | fd, err := C.opendir((*C.char)(unsafe.Pointer(name))) |
115 | if err != syscall.EINTR { |
116 | return fd, err |
117 | } |
118 | } |
119 | } |
120 |
Members