Home | History | Annotate | Download | only in fs
      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 
     15 package 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 
     22 import (
     23 	"os"
     24 	"syscall"
     25 	"unsafe"
     26 )
     27 
     28 const (
     29 	blockSize = 4096
     30 )
     31 
     32 func 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 
     81 func 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 
    125 func direntIno(buf []byte) (uint64, bool) {
    126 	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
    127 }
    128 
    129 func direntType(buf []byte) (uint64, bool) {
    130 	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Type), unsafe.Sizeof(syscall.Dirent{}.Type))
    131 }
    132 
    133 func direntReclen(buf []byte) (uint64, bool) {
    134 	return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
    135 }
    136 
    137 func 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.
    146 func 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 
    153 func 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.
    173 func 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
    183 const (
    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 
    194 func 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