1// Copyright 2016 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 aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris
6
7package fastwalk
8
9import (
10 "os"
11 "syscall"
12
13 "github.com/charlievieth/fastwalk/internal/dirent"
14)
15
16// More than 5760 to work around https://golang.org/issue/24015.
17const blockSize = 8192
18
19// unknownFileMode is a sentinel (and bogus) os.FileMode
20// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
21const unknownFileMode os.FileMode = ^os.FileMode(0)
22
23func (w *walker) readDir(dirName string, depth int) error {
24 fd, err := open(dirName, 0, 0)
25 if err != nil {
26 return &os.PathError{Op: "open", Path: dirName, Err: err}
27 }
28 defer syscall.Close(fd)
29
30 var p *[]*unixDirent
31 if w.sortMode != SortNone {
32 p = direntSlicePool.Get().(*[]*unixDirent)
33 }
34 defer putDirentSlice(p)
35
36 // The buffer must be at least a block long.
37 buf := make([]byte, blockSize) // stack-allocated; doesn't escape
38 bufp := 0 // starting read position in buf
39 nbuf := 0 // end valid data in buf
40 skipFiles := false
41 for {
42 if bufp >= nbuf {
43 bufp = 0
44 nbuf, err = readDirent(fd, buf)
45 if err != nil {
46 return os.NewSyscallError("readdirent", err)
47 }
48 if nbuf <= 0 {
49 break // exit loop
50 }
51 }
52 consumed, name, typ := dirent.Parse(buf[bufp:nbuf])
53 bufp += consumed
54
55 if name == "" || name == "." || name == ".." {
56 continue
57 }
58 // Fallback for filesystems (like old XFS) that don't
59 // support Dirent.Type and have DT_UNKNOWN (0) there
60 // instead.
61 if typ == unknownFileMode {
62 fi, err := os.Lstat(dirName + "/" + name)
63 if err != nil {
64 // It got deleted in the meantime.
65 if os.IsNotExist(err) {
66 continue
67 }
68 return err
69 }
70 typ = fi.Mode() & os.ModeType
71 }
72 if skipFiles && typ.IsRegular() {
73 continue
74 }
75 de := newUnixDirent(dirName, name, typ, depth)
76 if w.sortMode == SortNone {
77 if err := w.onDirEnt(dirName, name, de); err != nil {
78 if err == ErrSkipFiles {
79 skipFiles = true
80 continue
81 }
82 return err
83 }
84 } else {
85 *p = append(*p, de)
86 }
87 }
88 if w.sortMode == SortNone {
89 return nil
90 }
91
92 dents := *p
93 sortDirents(w.sortMode, dents)
94 for _, d := range dents {
95 d := d
96 if skipFiles && d.typ.IsRegular() {
97 continue
98 }
99 if err := w.onDirEnt(dirName, d.Name(), d); err != nil {
100 if err != ErrSkipFiles {
101 return err
102 }
103 skipFiles = true
104 }
105 }
106 return nil
107}
108
109// According to https://golang.org/doc/go1.14#runtime
110// A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS
111// systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases.
112//
113// This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors.
114// We need to retry in this case.
115func open(path string, mode int, perm uint32) (fd int, err error) {
116 for {
117 fd, err := syscall.Open(path, mode, perm)
118 if err != syscall.EINTR {
119 return fd, err
120 }
121 }
122}
123
124func readDirent(fd int, buf []byte) (n int, err error) {
125 for {
126 nbuf, err := syscall.ReadDirent(fd, buf)
127 if err != syscall.EINTR {
128 return nbuf, err
129 }
130 }
131}