github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/finder/fs/readdir.go (about)

     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  }