You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
5.3 KiB
220 lines
5.3 KiB
// 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
|
|
}
|