// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fs // This is based on the readdir implementation from Go 1.9: // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. import ( "os" "syscall" "unsafe" ) const ( blockSize = 4096 ) func readdir(path string) ([]DirEntryInfo, error) { f, err := os.Open(path) defer f.Close() if err != nil { return nil, err } // This implicitly switches the fd to non-blocking mode, which is less efficient than what // file.ReadDir does since it will keep a thread blocked and not just a goroutine. fd := int(f.Fd()) buf := make([]byte, blockSize) entries := make([]*dirEntryInfo, 0, 100) for { n, errno := syscall.ReadDirent(fd, buf) if errno != nil { err = os.NewSyscallError("readdirent", errno) break } if n <= 0 { break // EOF } entries = parseDirent(buf[:n], entries) } ret := make([]DirEntryInfo, 0, len(entries)) for _, entry := range entries { if !entry.modeExists { mode, lerr := lstatFileMode(path + "/" + entry.name) if os.IsNotExist(lerr) { // File disappeared between readdir + stat. // Just treat it as if it didn't exist. continue } if lerr != nil { return ret, lerr } entry.mode = mode entry.modeExists = true } ret = append(ret, entry) } return ret, err } func parseDirent(buf []byte, entries []*dirEntryInfo) []*dirEntryInfo { for len(buf) > 0 { reclen, ok := direntReclen(buf) if !ok || reclen > uint64(len(buf)) { return entries } rec := buf[:reclen] buf = buf[reclen:] ino, ok := direntIno(rec) if !ok { break } if ino == 0 { // File absent in directory. continue } typ, ok := direntType(rec) if !ok { break } const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) namlen, ok := direntNamlen(rec) if !ok || namoff+namlen > uint64(len(rec)) { break } name := rec[namoff : namoff+namlen] for i, c := range name { if c == 0 { name = name[:i] break } } // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { continue } mode, modeExists := direntTypeToFileMode(typ) entries = append(entries, &dirEntryInfo{string(name), mode, modeExists}) } return entries } func direntIno(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) } func direntType(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Type), unsafe.Sizeof(syscall.Dirent{}.Type)) } func direntReclen(buf []byte) (uint64, bool) { return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) } func direntNamlen(buf []byte) (uint64, bool) { reclen, ok := direntReclen(buf) if !ok { return 0, false } return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true } // readInt returns the size-bytes unsigned integer in native byte order at offset off. func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { if len(b) < int(off+size) { return 0, false } return readIntLE(b[off:], size), true } func readIntLE(b []byte, size uintptr) uint64 { switch size { case 1: return uint64(b[0]) case 2: _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 case 4: _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 case 8: _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 default: panic("syscall: readInt with unsupported size") } } // If the directory entry doesn't specify the type, fall back to using lstat to get the type. func lstatFileMode(name string) (os.FileMode, error) { stat, err := os.Lstat(name) if err != nil { return 0, err } return stat.Mode() & (os.ModeType | os.ModeCharDevice), nil } // from Linux and Darwin dirent.h const ( DT_UNKNOWN = 0 DT_FIFO = 1 DT_CHR = 2 DT_DIR = 4 DT_BLK = 6 DT_REG = 8 DT_LNK = 10 DT_SOCK = 12 ) func direntTypeToFileMode(typ uint64) (os.FileMode, bool) { exists := true var mode os.FileMode switch typ { case DT_UNKNOWN: exists = false case DT_FIFO: mode = os.ModeNamedPipe case DT_CHR: mode = os.ModeDevice | os.ModeCharDevice case DT_DIR: mode = os.ModeDir case DT_BLK: mode = os.ModeDevice case DT_REG: mode = 0 case DT_LNK: mode = os.ModeSymlink case DT_SOCK: mode = os.ModeSocket default: exists = false } return mode, exists }