1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package fs 16 17// This is based on the readdir implementation from Go 1.9: 18// Copyright 2009 The Go Authors. All rights reserved. 19// Use of this source code is governed by a BSD-style 20// license that can be found in the LICENSE file. 21 22import ( 23 "os" 24 "syscall" 25 "unsafe" 26) 27 28const ( 29 blockSize = 4096 30) 31 32func readdir(path string) ([]DirEntryInfo, error) { 33 f, err := os.Open(path) 34 defer f.Close() 35 36 if err != nil { 37 return nil, err 38 } 39 // This implicitly switches the fd to non-blocking mode, which is less efficient than what 40 // file.ReadDir does since it will keep a thread blocked and not just a goroutine. 41 fd := int(f.Fd()) 42 43 buf := make([]byte, blockSize) 44 entries := make([]*dirEntryInfo, 0, 100) 45 46 for { 47 n, errno := syscall.ReadDirent(fd, buf) 48 if errno != nil { 49 err = os.NewSyscallError("readdirent", errno) 50 break 51 } 52 if n <= 0 { 53 break // EOF 54 } 55 56 entries = parseDirent(buf[:n], entries) 57 } 58 59 ret := make([]DirEntryInfo, 0, len(entries)) 60 61 for _, entry := range entries { 62 if !entry.modeExists { 63 mode, lerr := lstatFileMode(path + "/" + entry.name) 64 if os.IsNotExist(lerr) { 65 // File disappeared between readdir + stat. 66 // Just treat it as if it didn't exist. 67 continue 68 } 69 if lerr != nil { 70 return ret, lerr 71 } 72 entry.mode = mode 73 entry.modeExists = true 74 } 75 ret = append(ret, entry) 76 } 77 78 return ret, err 79} 80 81func parseDirent(buf []byte, entries []*dirEntryInfo) []*dirEntryInfo { 82 for len(buf) > 0 { 83 reclen, ok := direntReclen(buf) 84 if !ok || reclen > uint64(len(buf)) { 85 return entries 86 } 87 rec := buf[:reclen] 88 buf = buf[reclen:] 89 ino, ok := direntIno(rec) 90 if !ok { 91 break 92 } 93 if ino == 0 { // File absent in directory. 94 continue 95 } 96 typ, ok := direntType(rec) 97 if !ok { 98 break 99 } 100 const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) 101 namlen, ok := direntNamlen(rec) 102 if !ok || namoff+namlen > uint64(len(rec)) { 103 break 104 } 105 name := rec[namoff : namoff+namlen] 106 107 for i, c := range name { 108 if c == 0 { 109 name = name[:i] 110 break 111 } 112 } 113 // Check for useless names before allocating a string. 114 if string(name) == "." || string(name) == ".." { 115 continue 116 } 117 118 mode, modeExists := direntTypeToFileMode(typ) 119 120 entries = append(entries, &dirEntryInfo{string(name), mode, modeExists}) 121 } 122 return entries 123} 124 125func direntIno(buf []byte) (uint64, bool) { 126 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) 127} 128 129func direntType(buf []byte) (uint64, bool) { 130 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Type), unsafe.Sizeof(syscall.Dirent{}.Type)) 131} 132 133func direntReclen(buf []byte) (uint64, bool) { 134 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) 135} 136 137func direntNamlen(buf []byte) (uint64, bool) { 138 reclen, ok := direntReclen(buf) 139 if !ok { 140 return 0, false 141 } 142 return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true 143} 144 145// readInt returns the size-bytes unsigned integer in native byte order at offset off. 146func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { 147 if len(b) < int(off+size) { 148 return 0, false 149 } 150 return readIntLE(b[off:], size), true 151} 152 153func readIntLE(b []byte, size uintptr) uint64 { 154 switch size { 155 case 1: 156 return uint64(b[0]) 157 case 2: 158 _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 159 return uint64(b[0]) | uint64(b[1])<<8 160 case 4: 161 _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 162 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 163 case 8: 164 _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 165 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | 166 uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 167 default: 168 panic("syscall: readInt with unsupported size") 169 } 170} 171 172// If the directory entry doesn't specify the type, fall back to using lstat to get the type. 173func lstatFileMode(name string) (os.FileMode, error) { 174 stat, err := os.Lstat(name) 175 if err != nil { 176 return 0, err 177 } 178 179 return stat.Mode() & (os.ModeType | os.ModeCharDevice), nil 180} 181 182// from Linux and Darwin dirent.h 183const ( 184 DT_UNKNOWN = 0 185 DT_FIFO = 1 186 DT_CHR = 2 187 DT_DIR = 4 188 DT_BLK = 6 189 DT_REG = 8 190 DT_LNK = 10 191 DT_SOCK = 12 192) 193 194func direntTypeToFileMode(typ uint64) (os.FileMode, bool) { 195 exists := true 196 var mode os.FileMode 197 switch typ { 198 case DT_UNKNOWN: 199 exists = false 200 case DT_FIFO: 201 mode = os.ModeNamedPipe 202 case DT_CHR: 203 mode = os.ModeDevice | os.ModeCharDevice 204 case DT_DIR: 205 mode = os.ModeDir 206 case DT_BLK: 207 mode = os.ModeDevice 208 case DT_REG: 209 mode = 0 210 case DT_LNK: 211 mode = os.ModeSymlink 212 case DT_SOCK: 213 mode = os.ModeSocket 214 default: 215 exists = false 216 } 217 218 return mode, exists 219} 220