1// Copyright 2019 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// +build darwin,go1.13 6 7package unix 8 9import ( 10 "unsafe" 11 12 "golang.org/x/sys/internal/unsafeheader" 13) 14 15//sys closedir(dir uintptr) (err error) 16//sys readdir_r(dir uintptr, entry *Dirent, result **Dirent) (res Errno) 17 18func fdopendir(fd int) (dir uintptr, err error) { 19 r0, _, e1 := syscall_syscallPtr(funcPC(libc_fdopendir_trampoline), uintptr(fd), 0, 0) 20 dir = uintptr(r0) 21 if e1 != 0 { 22 err = errnoErr(e1) 23 } 24 return 25} 26 27func libc_fdopendir_trampoline() 28 29//go:cgo_import_dynamic libc_fdopendir fdopendir "/usr/lib/libSystem.B.dylib" 30 31func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) { 32 // Simulate Getdirentries using fdopendir/readdir_r/closedir. 33 // We store the number of entries to skip in the seek 34 // offset of fd. See issue #31368. 35 // It's not the full required semantics, but should handle the case 36 // of calling Getdirentries or ReadDirent repeatedly. 37 // It won't handle assigning the results of lseek to *basep, or handle 38 // the directory being edited underfoot. 39 skip, err := Seek(fd, 0, 1 /* SEEK_CUR */) 40 if err != nil { 41 return 0, err 42 } 43 44 // We need to duplicate the incoming file descriptor 45 // because the caller expects to retain control of it, but 46 // fdopendir expects to take control of its argument. 47 // Just Dup'ing the file descriptor is not enough, as the 48 // result shares underlying state. Use Openat to make a really 49 // new file descriptor referring to the same directory. 50 fd2, err := Openat(fd, ".", O_RDONLY, 0) 51 if err != nil { 52 return 0, err 53 } 54 d, err := fdopendir(fd2) 55 if err != nil { 56 Close(fd2) 57 return 0, err 58 } 59 defer closedir(d) 60 61 var cnt int64 62 for { 63 var entry Dirent 64 var entryp *Dirent 65 e := readdir_r(d, &entry, &entryp) 66 if e != 0 { 67 return n, errnoErr(e) 68 } 69 if entryp == nil { 70 break 71 } 72 if skip > 0 { 73 skip-- 74 cnt++ 75 continue 76 } 77 78 reclen := int(entry.Reclen) 79 if reclen > len(buf) { 80 // Not enough room. Return for now. 81 // The counter will let us know where we should start up again. 82 // Note: this strategy for suspending in the middle and 83 // restarting is O(n^2) in the length of the directory. Oh well. 84 break 85 } 86 87 // Copy entry into return buffer. 88 var s []byte 89 hdr := (*unsafeheader.Slice)(unsafe.Pointer(&s)) 90 hdr.Data = unsafe.Pointer(&entry) 91 hdr.Cap = reclen 92 hdr.Len = reclen 93 copy(buf, s) 94 95 buf = buf[reclen:] 96 n += reclen 97 cnt++ 98 } 99 // Set the seek offset of the input fd to record 100 // how many files we've already returned. 101 _, err = Seek(fd, cnt, 0 /* SEEK_SET */) 102 if err != nil { 103 return n, err 104 } 105 106 return n, nil 107} 108