GoPLS Viewer

Home|gopls/internal/fastwalk/fastwalk_unix.go
1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build (linux || freebsd || openbsd || netbsd || (darwin && !cgo)) && !appengine
6// +build linux freebsd openbsd netbsd darwin,!cgo
7// +build !appengine
8
9package fastwalk
10
11import (
12    "fmt"
13    "os"
14    "syscall"
15    "unsafe"
16)
17
18const blockSize = 8 << 10
19
20// unknownFileMode is a sentinel (and bogus) os.FileMode
21// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
22const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
23
24func readDir(dirName stringfn func(dirNameentName stringtyp os.FileModeerrorerror {
25    fderr := open(dirName00)
26    if err != nil {
27        return &os.PathError{Op"open"PathdirNameErrerr}
28    }
29    defer syscall.Close(fd)
30
31    // The buffer must be at least a block long.
32    buf := make([]byteblockSize// stack-allocated; doesn't escape
33    bufp := 0                      // starting read position in buf
34    nbuf := 0                      // end valid data in buf
35    skipFiles := false
36    for {
37        if bufp >= nbuf {
38            bufp = 0
39            nbuferr = readDirent(fdbuf)
40            if err != nil {
41                return os.NewSyscallError("readdirent"err)
42            }
43            if nbuf <= 0 {
44                return nil
45            }
46        }
47        consumednametyp := parseDirEnt(buf[bufp:nbuf])
48        bufp += consumed
49        if name == "" || name == "." || name == ".." {
50            continue
51        }
52        // Fallback for filesystems (like old XFS) that don't
53        // support Dirent.Type and have DT_UNKNOWN (0) there
54        // instead.
55        if typ == unknownFileMode {
56            fierr := os.Lstat(dirName + "/" + name)
57            if err != nil {
58                // It got deleted in the meantime.
59                if os.IsNotExist(err) {
60                    continue
61                }
62                return err
63            }
64            typ = fi.Mode() & os.ModeType
65        }
66        if skipFiles && typ.IsRegular() {
67            continue
68        }
69        if err := fn(dirNamenametyp); err != nil {
70            if err == ErrSkipFiles {
71                skipFiles = true
72                continue
73            }
74            return err
75        }
76    }
77}
78
79func parseDirEnt(buf []byte) (consumed intname stringtyp os.FileMode) {
80    // golang.org/issue/37269
81    dirent := &syscall.Dirent{}
82    copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf)
83    if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
84        panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d"len(buf), v))
85    }
86    if len(buf) < int(dirent.Reclen) {
87        panic(fmt.Sprintf("buf size %d < record length %d"len(buf), dirent.Reclen))
88    }
89    consumed = int(dirent.Reclen)
90    if direntInode(dirent) == 0 { // File absent in directory.
91        return
92    }
93    switch dirent.Type {
94    case syscall.DT_REG:
95        typ = 0
96    case syscall.DT_DIR:
97        typ = os.ModeDir
98    case syscall.DT_LNK:
99        typ = os.ModeSymlink
100    case syscall.DT_BLK:
101        typ = os.ModeDevice
102    case syscall.DT_FIFO:
103        typ = os.ModeNamedPipe
104    case syscall.DT_SOCK:
105        typ = os.ModeSocket
106    case syscall.DT_UNKNOWN:
107        typ = unknownFileMode
108    default:
109        // Skip weird things.
110        // It's probably a DT_WHT (http://lwn.net/Articles/325369/)
111        // or something. Revisit if/when this package is moved outside
112        // of goimports. goimports only cares about regular files,
113        // symlinks, and directories.
114        return
115    }
116
117    nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
118    nameLen := direntNamlen(dirent)
119
120    // Special cases for common things:
121    if nameLen == 1 && nameBuf[0] == '.' {
122        name = "."
123    } else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
124        name = ".."
125    } else {
126        name = string(nameBuf[:nameLen])
127    }
128    return
129}
130
131// According to https://golang.org/doc/go1.14#runtime
132// A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS
133// systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases.
134//
135// This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors.
136// We need to retry in this case.
137func open(path stringmode intperm uint32) (fd interr error) {
138    for {
139        fderr := syscall.Open(pathmodeperm)
140        if err != syscall.EINTR {
141            return fderr
142        }
143    }
144}
145
146func readDirent(fd intbuf []byte) (n interr error) {
147    for {
148        nbuferr := syscall.ReadDirent(fdbuf)
149        if err != syscall.EINTR {
150            return nbuferr
151        }
152    }
153}
154
MembersX
readDirent.BlockStmt.nbuf
readDirent
readDir.BlockStmt.BlockStmt.fi
open.mode
open.fd
open.BlockStmt.err
open.err
unknownFileMode
readDir.BlockStmt.consumed
parseDirEnt.dirent
parseDirEnt.nameLen
readDir.nbuf
open
readDirent.fd
parseDirEnt.buf
parseDirEnt.consumed
parseDirEnt.typ
open.BlockStmt.fd
readDir.bufp
readDir.BlockStmt.name
readDir.BlockStmt.BlockStmt.err
parseDirEnt
readDirent.buf
open.path
open.perm
readDirent.err
readDirent.BlockStmt.err
readDir.buf
parseDirEnt.name
parseDirEnt.nameBuf
readDirent.n
Members
X