Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / godoc / vfs / namespace.go
1 // Copyright 2011 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 package vfs
6
7 import (
8         "fmt"
9         "io"
10         "os"
11         pathpkg "path"
12         "sort"
13         "strings"
14         "time"
15 )
16
17 // Setting debugNS = true will enable debugging prints about
18 // name space translations.
19 const debugNS = false
20
21 // A NameSpace is a file system made up of other file systems
22 // mounted at specific locations in the name space.
23 //
24 // The representation is a map from mount point locations
25 // to the list of file systems mounted at that location.  A traditional
26 // Unix mount table would use a single file system per mount point,
27 // but we want to be able to mount multiple file systems on a single
28 // mount point and have the system behave as if the union of those
29 // file systems were present at the mount point.
30 // For example, if the OS file system has a Go installation in
31 // c:\Go and additional Go path trees in d:\Work1 and d:\Work2, then
32 // this name space creates the view we want for the godoc server:
33 //
34 //      NameSpace{
35 //              "/": {
36 //                      {old: "/", fs: OS(`c:\Go`), new: "/"},
37 //              },
38 //              "/src/pkg": {
39 //                      {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
40 //                      {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
41 //                      {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
42 //              },
43 //      }
44 //
45 // This is created by executing:
46 //
47 //      ns := NameSpace{}
48 //      ns.Bind("/", OS(`c:\Go`), "/", BindReplace)
49 //      ns.Bind("/src/pkg", OS(`d:\Work1`), "/src", BindAfter)
50 //      ns.Bind("/src/pkg", OS(`d:\Work2`), "/src", BindAfter)
51 //
52 // A particular mount point entry is a triple (old, fs, new), meaning that to
53 // operate on a path beginning with old, replace that prefix (old) with new
54 // and then pass that path to the FileSystem implementation fs.
55 //
56 // If you do not explicitly mount a FileSystem at the root mountpoint "/" of the
57 // NameSpace like above, Stat("/") will return a "not found" error which could
58 // break typical directory traversal routines. In such cases, use NewNameSpace()
59 // to get a NameSpace pre-initialized with an emulated empty directory at root.
60 //
61 // Given this name space, a ReadDir of /src/pkg/code will check each prefix
62 // of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src,
63 // then /), stopping when it finds one.  For the above example, /src/pkg/code
64 // will find the mount point at /src/pkg:
65 //
66 //      {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
67 //      {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
68 //      {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
69 //
70 // ReadDir will when execute these three calls and merge the results:
71 //
72 //      OS(`c:\Go`).ReadDir("/src/pkg/code")
73 //      OS(`d:\Work1').ReadDir("/src/code")
74 //      OS(`d:\Work2').ReadDir("/src/code")
75 //
76 // Note that the "/src/pkg" in "/src/pkg/code" has been replaced by
77 // just "/src" in the final two calls.
78 //
79 // OS is itself an implementation of a file system: it implements
80 // OS(`c:\Go`).ReadDir("/src/pkg/code") as ioutil.ReadDir(`c:\Go\src\pkg\code`).
81 //
82 // Because the new path is evaluated by fs (here OS(root)), another way
83 // to read the mount table is to mentally combine fs+new, so that this table:
84 //
85 //      {old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
86 //      {old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
87 //      {old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
88 //
89 // reads as:
90 //
91 //      "/src/pkg" -> c:\Go\src\pkg
92 //      "/src/pkg" -> d:\Work1\src
93 //      "/src/pkg" -> d:\Work2\src
94 //
95 // An invariant (a redundancy) of the name space representation is that
96 // ns[mtpt][i].old is always equal to mtpt (in the example, ns["/src/pkg"]'s
97 // mount table entries always have old == "/src/pkg").  The 'old' field is
98 // useful to callers, because they receive just a []mountedFS and not any
99 // other indication of which mount point was found.
100 //
101 type NameSpace map[string][]mountedFS
102
103 // A mountedFS handles requests for path by replacing
104 // a prefix 'old' with 'new' and then calling the fs methods.
105 type mountedFS struct {
106         old string
107         fs  FileSystem
108         new string
109 }
110
111 // hasPathPrefix reports whether x == y or x == y + "/" + more.
112 func hasPathPrefix(x, y string) bool {
113         return x == y || strings.HasPrefix(x, y) && (strings.HasSuffix(y, "/") || strings.HasPrefix(x[len(y):], "/"))
114 }
115
116 // translate translates path for use in m, replacing old with new.
117 //
118 // mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code".
119 func (m mountedFS) translate(path string) string {
120         path = pathpkg.Clean("/" + path)
121         if !hasPathPrefix(path, m.old) {
122                 panic("translate " + path + " but old=" + m.old)
123         }
124         return pathpkg.Join(m.new, path[len(m.old):])
125 }
126
127 func (NameSpace) String() string {
128         return "ns"
129 }
130
131 // Fprint writes a text representation of the name space to w.
132 func (ns NameSpace) Fprint(w io.Writer) {
133         fmt.Fprint(w, "name space {\n")
134         var all []string
135         for mtpt := range ns {
136                 all = append(all, mtpt)
137         }
138         sort.Strings(all)
139         for _, mtpt := range all {
140                 fmt.Fprintf(w, "\t%s:\n", mtpt)
141                 for _, m := range ns[mtpt] {
142                         fmt.Fprintf(w, "\t\t%s %s\n", m.fs, m.new)
143                 }
144         }
145         fmt.Fprint(w, "}\n")
146 }
147
148 // clean returns a cleaned, rooted path for evaluation.
149 // It canonicalizes the path so that we can use string operations
150 // to analyze it.
151 func (NameSpace) clean(path string) string {
152         return pathpkg.Clean("/" + path)
153 }
154
155 type BindMode int
156
157 const (
158         BindReplace BindMode = iota
159         BindBefore
160         BindAfter
161 )
162
163 // Bind causes references to old to redirect to the path new in newfs.
164 // If mode is BindReplace, old redirections are discarded.
165 // If mode is BindBefore, this redirection takes priority over existing ones,
166 // but earlier ones are still consulted for paths that do not exist in newfs.
167 // If mode is BindAfter, this redirection happens only after existing ones
168 // have been tried and failed.
169 func (ns NameSpace) Bind(old string, newfs FileSystem, new string, mode BindMode) {
170         old = ns.clean(old)
171         new = ns.clean(new)
172         m := mountedFS{old, newfs, new}
173         var mtpt []mountedFS
174         switch mode {
175         case BindReplace:
176                 mtpt = append(mtpt, m)
177         case BindAfter:
178                 mtpt = append(mtpt, ns.resolve(old)...)
179                 mtpt = append(mtpt, m)
180         case BindBefore:
181                 mtpt = append(mtpt, m)
182                 mtpt = append(mtpt, ns.resolve(old)...)
183         }
184
185         // Extend m.old, m.new in inherited mount point entries.
186         for i := range mtpt {
187                 m := &mtpt[i]
188                 if m.old != old {
189                         if !hasPathPrefix(old, m.old) {
190                                 // This should not happen.  If it does, panic so
191                                 // that we can see the call trace that led to it.
192                                 panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}", old, m.old, m.fs.String(), m.new))
193                         }
194                         suffix := old[len(m.old):]
195                         m.old = pathpkg.Join(m.old, suffix)
196                         m.new = pathpkg.Join(m.new, suffix)
197                 }
198         }
199
200         ns[old] = mtpt
201 }
202
203 // resolve resolves a path to the list of mountedFS to use for path.
204 func (ns NameSpace) resolve(path string) []mountedFS {
205         path = ns.clean(path)
206         for {
207                 if m := ns[path]; m != nil {
208                         if debugNS {
209                                 fmt.Printf("resolve %s: %v\n", path, m)
210                         }
211                         return m
212                 }
213                 if path == "/" {
214                         break
215                 }
216                 path = pathpkg.Dir(path)
217         }
218         return nil
219 }
220
221 // Open implements the FileSystem Open method.
222 func (ns NameSpace) Open(path string) (ReadSeekCloser, error) {
223         var err error
224         for _, m := range ns.resolve(path) {
225                 if debugNS {
226                         fmt.Printf("tx %s: %v\n", path, m.translate(path))
227                 }
228                 tp := m.translate(path)
229                 r, err1 := m.fs.Open(tp)
230                 if err1 == nil {
231                         return r, nil
232                 }
233                 // IsNotExist errors in overlay FSes can mask real errors in
234                 // the underlying FS, so ignore them if there is another error.
235                 if err == nil || os.IsNotExist(err) {
236                         err = err1
237                 }
238         }
239         if err == nil {
240                 err = &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
241         }
242         return nil, err
243 }
244
245 // stat implements the FileSystem Stat and Lstat methods.
246 func (ns NameSpace) stat(path string, f func(FileSystem, string) (os.FileInfo, error)) (os.FileInfo, error) {
247         var err error
248         for _, m := range ns.resolve(path) {
249                 fi, err1 := f(m.fs, m.translate(path))
250                 if err1 == nil {
251                         return fi, nil
252                 }
253                 if err == nil {
254                         err = err1
255                 }
256         }
257         if err == nil {
258                 err = &os.PathError{Op: "stat", Path: path, Err: os.ErrNotExist}
259         }
260         return nil, err
261 }
262
263 func (ns NameSpace) Stat(path string) (os.FileInfo, error) {
264         return ns.stat(path, FileSystem.Stat)
265 }
266
267 func (ns NameSpace) Lstat(path string) (os.FileInfo, error) {
268         return ns.stat(path, FileSystem.Lstat)
269 }
270
271 // dirInfo is a trivial implementation of os.FileInfo for a directory.
272 type dirInfo string
273
274 func (d dirInfo) Name() string       { return string(d) }
275 func (d dirInfo) Size() int64        { return 0 }
276 func (d dirInfo) Mode() os.FileMode  { return os.ModeDir | 0555 }
277 func (d dirInfo) ModTime() time.Time { return startTime }
278 func (d dirInfo) IsDir() bool        { return true }
279 func (d dirInfo) Sys() interface{}   { return nil }
280
281 var startTime = time.Now()
282
283 // ReadDir implements the FileSystem ReadDir method.  It's where most of the magic is.
284 // (The rest is in resolve.)
285 //
286 // Logically, ReadDir must return the union of all the directories that are named
287 // by path.  In order to avoid misinterpreting Go packages, of all the directories
288 // that contain Go source code, we only include the files from the first,
289 // but we include subdirectories from all.
290 //
291 // ReadDir must also return directory entries needed to reach mount points.
292 // If the name space looks like the example in the type NameSpace comment,
293 // but c:\Go does not have a src/pkg subdirectory, we still want to be able
294 // to find that subdirectory, because we've mounted d:\Work1 and d:\Work2
295 // there.  So if we don't see "src" in the directory listing for c:\Go, we add an
296 // entry for it before returning.
297 //
298 func (ns NameSpace) ReadDir(path string) ([]os.FileInfo, error) {
299         path = ns.clean(path)
300
301         // List matching directories and determine whether any of them contain
302         // Go files.
303         var (
304                 dirs       [][]os.FileInfo
305                 goDirIndex = -1
306                 readDirErr error
307         )
308
309         for _, m := range ns.resolve(path) {
310                 dir, err := m.fs.ReadDir(m.translate(path))
311                 if err != nil {
312                         if readDirErr == nil {
313                                 readDirErr = err
314                         }
315                         continue
316                 }
317
318                 dirs = append(dirs, dir)
319
320                 if goDirIndex < 0 {
321                         for _, f := range dir {
322                                 if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
323                                         goDirIndex = len(dirs) - 1
324                                         break
325                                 }
326                         }
327                 }
328         }
329
330         // Build a list of files and subdirectories. If a directory contains Go files,
331         // only include files from that directory. Otherwise, include files from
332         // all directories. Include subdirectories from all directories regardless
333         // of whether Go files are present.
334         haveName := make(map[string]bool)
335         var all []os.FileInfo
336         for i, dir := range dirs {
337                 for _, f := range dir {
338                         name := f.Name()
339                         if !haveName[name] && (f.IsDir() || goDirIndex < 0 || goDirIndex == i) {
340                                 all = append(all, f)
341                                 haveName[name] = true
342                         }
343                 }
344         }
345
346         // Add any missing directories needed to reach mount points.
347         for old := range ns {
348                 if hasPathPrefix(old, path) && old != path {
349                         // Find next element after path in old.
350                         elem := old[len(path):]
351                         elem = strings.TrimPrefix(elem, "/")
352                         if i := strings.Index(elem, "/"); i >= 0 {
353                                 elem = elem[:i]
354                         }
355                         if !haveName[elem] {
356                                 haveName[elem] = true
357                                 all = append(all, dirInfo(elem))
358                         }
359                 }
360         }
361
362         if len(all) == 0 {
363                 return nil, readDirErr
364         }
365
366         sort.Sort(byName(all))
367         return all, nil
368 }
369
370 // RootType returns the RootType for the given path in the namespace.
371 func (ns NameSpace) RootType(path string) RootType {
372         // We resolve the given path to a list of mountedFS and then return
373         // the root type for the filesystem which contains the path.
374         for _, m := range ns.resolve(path) {
375                 _, err := m.fs.ReadDir(m.translate(path))
376                 // Found a match, return the filesystem's root type
377                 if err == nil {
378                         return m.fs.RootType(path)
379                 }
380         }
381         return ""
382 }
383
384 // byName implements sort.Interface.
385 type byName []os.FileInfo
386
387 func (f byName) Len() int           { return len(f) }
388 func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
389 func (f byName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }