Skip to content

Commit a6af104

Browse files
committed
syscall: store skip count in file descriptor offset
Multiple calls to ReadDirent expect to return subsequent portions of the directory listing. There's no place to store our progress other than the file descriptor offset. Fortunately, the file descriptor offset doesn't need to be a real offset. We can store any int64 we want there. Fixes #31368 Change-Id: I49e4e0e7ff707d3e96aa5d43e3b0199531013cde Reviewed-on: https://go-review.googlesource.com/c/go/+/171477 Run-TryBot: Keith Randall <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent fda5e6d commit a6af104

File tree

2 files changed

+82
-11
lines changed

2 files changed

+82
-11
lines changed

src/syscall/dirent_bsd_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package syscall_test
88

99
import (
1010
"bytes"
11+
"fmt"
1112
"io/ioutil"
1213
"os"
1314
"path/filepath"
@@ -16,6 +17,7 @@ import (
1617
"strings"
1718
"syscall"
1819
"testing"
20+
"unsafe"
1921
)
2022

2123
func TestDirent(t *testing.T) {
@@ -41,10 +43,10 @@ func TestDirent(t *testing.T) {
4143

4244
buf := bytes.Repeat([]byte("DEADBEAF"), direntBufSize/8)
4345
fd, err := syscall.Open(d, syscall.O_RDONLY, 0)
44-
defer syscall.Close(fd)
4546
if err != nil {
4647
t.Fatalf("syscall.open: %v", err)
4748
}
49+
defer syscall.Close(fd)
4850
n, err := syscall.ReadDirent(fd, buf)
4951
if err != nil {
5052
t.Fatalf("syscall.readdir: %v", err)
@@ -74,3 +76,58 @@ func TestDirent(t *testing.T) {
7476
}
7577
}
7678
}
79+
80+
func TestDirentRepeat(t *testing.T) {
81+
const N = 100
82+
83+
// Make a directory containing N files
84+
d, err := ioutil.TempDir("", "direntRepeat-test")
85+
if err != nil {
86+
t.Fatalf("tempdir: %v", err)
87+
}
88+
defer os.RemoveAll(d)
89+
90+
var files []string
91+
for i := 0; i < N; i++ {
92+
files = append(files, fmt.Sprintf("file%d", i))
93+
}
94+
for _, file := range files {
95+
err = ioutil.WriteFile(filepath.Join(d, file), []byte("contents"), 0644)
96+
if err != nil {
97+
t.Fatalf("writefile: %v", err)
98+
}
99+
}
100+
101+
// Read the directory entries using ReadDirent.
102+
fd, err := syscall.Open(d, syscall.O_RDONLY, 0)
103+
if err != nil {
104+
t.Fatalf("syscall.open: %v", err)
105+
}
106+
defer syscall.Close(fd)
107+
var files2 []string
108+
for {
109+
// Note: the buf is small enough that this loop will need to
110+
// execute multiple times. See issue #31368.
111+
buf := make([]byte, N*unsafe.Offsetof(syscall.Dirent{}.Name)/4)
112+
n, err := syscall.ReadDirent(fd, buf)
113+
if err != nil {
114+
t.Fatalf("syscall.readdir: %v", err)
115+
}
116+
if n == 0 {
117+
break
118+
}
119+
buf = buf[:n]
120+
for len(buf) > 0 {
121+
var consumed int
122+
consumed, _, files2 = syscall.ParseDirent(buf, -1, files2)
123+
buf = buf[consumed:]
124+
}
125+
}
126+
127+
// Check results
128+
sort.Strings(files)
129+
sort.Strings(files2)
130+
if strings.Join(files, "|") != strings.Join(files2, "|") {
131+
t.Errorf("bad file list: want\n%q\ngot\n%q", files, files2)
132+
}
133+
}

src/syscall/syscall_darwin.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,18 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) {
368368
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
369369
// Simulate Getdirentries using fdopendir/readdir_r/closedir.
370370
const ptrSize = unsafe.Sizeof(uintptr(0))
371+
372+
// We store the number of entries to skip in the seek
373+
// offset of fd. See issue #31368.
374+
// It's not the full required semantics, but should handle the case
375+
// of calling Getdirentries or ReadDirent repeatedly.
376+
// It won't handle assigning the results of lseek to *basep, or handle
377+
// the directory being edited underfoot.
378+
skip, err := Seek(fd, 0, 1 /* SEEK_CUR */)
379+
if err != nil {
380+
return 0, err
381+
}
382+
371383
// We need to duplicate the incoming file descriptor
372384
// because the caller expects to retain control of it, but
373385
// fdopendir expects to take control of its argument.
@@ -384,13 +396,8 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
384396
return 0, err
385397
}
386398
defer closedir(d)
387-
// We keep the number of records already returned in *basep.
388-
// It's not the full required semantics, but should handle the case
389-
// of calling Getdirentries repeatedly.
390-
// It won't handle assigning the results of lseek to *basep, or handle
391-
// the directory being edited underfoot.
392-
skip := *basep
393-
*basep = 0
399+
400+
var cnt int64
394401
for {
395402
var entry Dirent
396403
var entryp *Dirent
@@ -403,13 +410,13 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
403410
}
404411
if skip > 0 {
405412
skip--
406-
*basep++
413+
cnt++
407414
continue
408415
}
409416
reclen := int(entry.Reclen)
410417
if reclen > len(buf) {
411418
// Not enough room. Return for now.
412-
// *basep will let us know where we should start up again.
419+
// The counter will let us know where we should start up again.
413420
// Note: this strategy for suspending in the middle and
414421
// restarting is O(n^2) in the length of the directory. Oh well.
415422
break
@@ -423,8 +430,15 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
423430
copy(buf, *(*[]byte)(unsafe.Pointer(&s)))
424431
buf = buf[reclen:]
425432
n += reclen
426-
*basep++
433+
cnt++
427434
}
435+
// Set the seek offset of the input fd to record
436+
// how many files we've already returned.
437+
_, err = Seek(fd, cnt, 0 /* SEEK_SET */)
438+
if err != nil {
439+
return n, err
440+
}
441+
428442
return n, nil
429443
}
430444

0 commit comments

Comments
 (0)