GoPLS Viewer

Home|gopls/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
5package vfs
6
7import (
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.
19const 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.
100type NameSpace map[string][]mountedFS
101
102// A mountedFS handles requests for path by replacing
103// a prefix 'old' with 'new' and then calling the fs methods.
104type mountedFS struct {
105    old string
106    fs  FileSystem
107    new string
108}
109
110// hasPathPrefix reports whether x == y or x == y + "/" + more.
111func hasPathPrefix(xy stringbool {
112    return x == y || strings.HasPrefix(xy) && (strings.HasSuffix(y"/") || strings.HasPrefix(x[len(y):], "/"))
113}
114
115// translate translates path for use in m, replacing old with new.
116//
117// mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code".
118func (m mountedFStranslate(path stringstring {
119    path = pathpkg.Clean("/" + path)
120    if !hasPathPrefix(pathm.old) {
121        panic("translate " + path + " but old=" + m.old)
122    }
123    return pathpkg.Join(m.newpath[len(m.old):])
124}
125
126func (NameSpaceString() string {
127    return "ns"
128}
129
130// Fprint writes a text representation of the name space to w.
131func (ns NameSpaceFprint(w io.Writer) {
132    fmt.Fprint(w"name space {\n")
133    var all []string
134    for mtpt := range ns {
135        all = append(allmtpt)
136    }
137    sort.Strings(all)
138    for _mtpt := range all {
139        fmt.Fprintf(w"\t%s:\n"mtpt)
140        for _m := range ns[mtpt] {
141            fmt.Fprintf(w"\t\t%s %s\n"m.fsm.new)
142        }
143    }
144    fmt.Fprint(w"}\n")
145}
146
147// clean returns a cleaned, rooted path for evaluation.
148// It canonicalizes the path so that we can use string operations
149// to analyze it.
150func (NameSpaceclean(path stringstring {
151    return pathpkg.Clean("/" + path)
152}
153
154type BindMode int
155
156const (
157    BindReplace BindMode = iota
158    BindBefore
159    BindAfter
160)
161
162// Bind causes references to old to redirect to the path new in newfs.
163// If mode is BindReplace, old redirections are discarded.
164// If mode is BindBefore, this redirection takes priority over existing ones,
165// but earlier ones are still consulted for paths that do not exist in newfs.
166// If mode is BindAfter, this redirection happens only after existing ones
167// have been tried and failed.
168func (ns NameSpaceBind(old stringnewfs FileSystemnew stringmode BindMode) {
169    old = ns.clean(old)
170    new = ns.clean(new)
171    m := mountedFS{oldnewfsnew}
172    var mtpt []mountedFS
173    switch mode {
174    case BindReplace:
175        mtpt = append(mtptm)
176    case BindAfter:
177        mtpt = append(mtptns.resolve(old)...)
178        mtpt = append(mtptm)
179    case BindBefore:
180        mtpt = append(mtptm)
181        mtpt = append(mtptns.resolve(old)...)
182    }
183
184    // Extend m.old, m.new in inherited mount point entries.
185    for i := range mtpt {
186        m := &mtpt[i]
187        if m.old != old {
188            if !hasPathPrefix(oldm.old) {
189                // This should not happen.  If it does, panic so
190                // that we can see the call trace that led to it.
191                panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}"oldm.oldm.fs.String(), m.new))
192            }
193            suffix := old[len(m.old):]
194            m.old = pathpkg.Join(m.oldsuffix)
195            m.new = pathpkg.Join(m.newsuffix)
196        }
197    }
198
199    ns[old] = mtpt
200}
201
202// resolve resolves a path to the list of mountedFS to use for path.
203func (ns NameSpaceresolve(path string) []mountedFS {
204    path = ns.clean(path)
205    for {
206        if m := ns[path]; m != nil {
207            if debugNS {
208                fmt.Printf("resolve %s: %v\n"pathm)
209            }
210            return m
211        }
212        if path == "/" {
213            break
214        }
215        path = pathpkg.Dir(path)
216    }
217    return nil
218}
219
220// Open implements the FileSystem Open method.
221func (ns NameSpaceOpen(path string) (ReadSeekClosererror) {
222    var err error
223    for _m := range ns.resolve(path) {
224        if debugNS {
225            fmt.Printf("tx %s: %v\n"pathm.translate(path))
226        }
227        tp := m.translate(path)
228        rerr1 := m.fs.Open(tp)
229        if err1 == nil {
230            return rnil
231        }
232        // IsNotExist errors in overlay FSes can mask real errors in
233        // the underlying FS, so ignore them if there is another error.
234        if err == nil || os.IsNotExist(err) {
235            err = err1
236        }
237    }
238    if err == nil {
239        err = &os.PathError{Op"open"PathpathErros.ErrNotExist}
240    }
241    return nilerr
242}
243
244// stat implements the FileSystem Stat and Lstat methods.
245func (ns NameSpacestat(path stringf func(FileSystemstring) (os.FileInfoerror)) (os.FileInfoerror) {
246    var err error
247    for _m := range ns.resolve(path) {
248        fierr1 := f(m.fsm.translate(path))
249        if err1 == nil {
250            return finil
251        }
252        if err == nil {
253            err = err1
254        }
255    }
256    if err == nil {
257        err = &os.PathError{Op"stat"PathpathErros.ErrNotExist}
258    }
259    return nilerr
260}
261
262func (ns NameSpaceStat(path string) (os.FileInfoerror) {
263    return ns.stat(pathFileSystem.Stat)
264}
265
266func (ns NameSpaceLstat(path string) (os.FileInfoerror) {
267    return ns.stat(pathFileSystem.Lstat)
268}
269
270// dirInfo is a trivial implementation of os.FileInfo for a directory.
271type dirInfo string
272
273func (d dirInfoName() string       { return string(d) }
274func (d dirInfoSize() int64        { return 0 }
275func (d dirInfoMode() os.FileMode  { return os.ModeDir | 0555 }
276func (d dirInfoModTime() time.Time { return startTime }
277func (d dirInfoIsDir() bool        { return true }
278func (d dirInfoSys() interface{}   { return nil }
279
280var startTime = time.Now()
281
282// ReadDir implements the FileSystem ReadDir method.  It's where most of the magic is.
283// (The rest is in resolve.)
284//
285// Logically, ReadDir must return the union of all the directories that are named
286// by path.  In order to avoid misinterpreting Go packages, of all the directories
287// that contain Go source code, we only include the files from the first,
288// but we include subdirectories from all.
289//
290// ReadDir must also return directory entries needed to reach mount points.
291// If the name space looks like the example in the type NameSpace comment,
292// but c:\Go does not have a src/pkg subdirectory, we still want to be able
293// to find that subdirectory, because we've mounted d:\Work1 and d:\Work2
294// there.  So if we don't see "src" in the directory listing for c:\Go, we add an
295// entry for it before returning.
296func (ns NameSpaceReadDir(path string) ([]os.FileInfoerror) {
297    path = ns.clean(path)
298
299    // List matching directories and determine whether any of them contain
300    // Go files.
301    var (
302        dirs       [][]os.FileInfo
303        goDirIndex = -1
304        readDirErr error
305    )
306
307    for _m := range ns.resolve(path) {
308        direrr := m.fs.ReadDir(m.translate(path))
309        if err != nil {
310            if readDirErr == nil {
311                readDirErr = err
312            }
313            continue
314        }
315
316        dirs = append(dirsdir)
317
318        if goDirIndex < 0 {
319            for _f := range dir {
320                if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
321                    goDirIndex = len(dirs) - 1
322                    break
323                }
324            }
325        }
326    }
327
328    // Build a list of files and subdirectories. If a directory contains Go files,
329    // only include files from that directory. Otherwise, include files from
330    // all directories. Include subdirectories from all directories regardless
331    // of whether Go files are present.
332    haveName := make(map[string]bool)
333    var all []os.FileInfo
334    for idir := range dirs {
335        for _f := range dir {
336            name := f.Name()
337            if !haveName[name] && (f.IsDir() || goDirIndex < 0 || goDirIndex == i) {
338                all = append(allf)
339                haveName[name] = true
340            }
341        }
342    }
343
344    // Add any missing directories needed to reach mount points.
345    for old := range ns {
346        if hasPathPrefix(oldpath) && old != path {
347            // Find next element after path in old.
348            elem := old[len(path):]
349            elem = strings.TrimPrefix(elem"/")
350            if i := strings.Index(elem"/"); i >= 0 {
351                elem = elem[:i]
352            }
353            if !haveName[elem] {
354                haveName[elem] = true
355                all = append(alldirInfo(elem))
356            }
357        }
358    }
359
360    if len(all) == 0 {
361        return nilreadDirErr
362    }
363
364    sort.Sort(byName(all))
365    return allnil
366}
367
368// RootType returns the RootType for the given path in the namespace.
369func (ns NameSpaceRootType(path stringRootType {
370    // We resolve the given path to a list of mountedFS and then return
371    // the root type for the filesystem which contains the path.
372    for _m := range ns.resolve(path) {
373        _err := m.fs.ReadDir(m.translate(path))
374        // Found a match, return the filesystem's root type
375        if err == nil {
376            return m.fs.RootType(path)
377        }
378    }
379    return ""
380}
381
382// byName implements sort.Interface.
383type byName []os.FileInfo
384
385func (f byNameLen() int           { return len(f) }
386func (f byNameLess(ij intbool { return f[i].Name() < f[j].Name() }
387func (f byNameSwap(ij int)      { f[i], f[j] = f[j], f[i] }
388
MembersX
NameSpace.stat.RangeStmt_7815.BlockStmt.err1
byName.Swap.j
mountedFS.fs
BindReplace
dirInfo.Size.d
pathpkg
NameSpace.ReadDir.RangeStmt_9821.BlockStmt.dir
NameSpace.RootType.RangeStmt_11488.BlockStmt._
mountedFS.translate
NameSpace.resolve.ns
NameSpace.ReadDir.haveName
byName.Less.f
NameSpace.Bind.old
NameSpace.Open.path
byName.Len
mountedFS.translate.m
byName
NameSpace.resolve.path
NameSpace.stat.err
NameSpace.Open.RangeStmt_7131.m
dirInfo.Name.d
dirInfo.Sys
NameSpace
byName.Len.f
NameSpace.Bind.ns
NameSpace.Bind.mode
byName.Less.i
hasPathPrefix.y
NameSpace.Fprint.RangeStmt_4815.mtpt
NameSpace.Fprint.all
NameSpace.ReadDir.RangeStmt_9821.m
NameSpace.Open.RangeStmt_7131.BlockStmt.err1
NameSpace.Lstat
sort
NameSpace.resolve
io
NameSpace.stat
dirInfo.ModTime
NameSpace.ReadDir.RangeStmt_10790.BlockStmt.BlockStmt.i
NameSpace.Fprint.RangeStmt_4887.BlockStmt.RangeStmt_4950.m
BindMode
NameSpace.clean.path
dirInfo.Sys.d
NameSpace.ReadDir.RangeStmt_9821.BlockStmt.err
hasPathPrefix
NameSpace.String
NameSpace.RootType
NameSpace.RootType.RangeStmt_11488.m
debugNS
NameSpace.Lstat.path
byName.Swap.f
dirInfo.IsDir
NameSpace.ReadDir
dirInfo.Mode.d
NameSpace.ReadDir.dirs
NameSpace.ReadDir.RangeStmt_10790.old
NameSpace.RootType.ns
NameSpace.Bind
NameSpace.Stat.ns
dirInfo.Mode
dirInfo.ModTime.d
NameSpace.ReadDir.RangeStmt_10514.BlockStmt.RangeStmt_10543.f
NameSpace.stat.RangeStmt_7815.m
byName.Less.j
NameSpace.Open.ns
NameSpace.Lstat.ns
NameSpace.RootType.path
NameSpace.Bind.mtpt
NameSpace.ReadDir.RangeStmt_10514.dir
NameSpace.RootType.RangeStmt_11488.BlockStmt.err
hasPathPrefix.x
NameSpace.Bind.RangeStmt_6225.i
NameSpace.Open
NameSpace.stat.RangeStmt_7815.BlockStmt.fi
NameSpace.Bind.newfs
NameSpace.stat.f
byName.Swap
NameSpace.stat.ns
NameSpace.stat.path
NameSpace.Stat
dirInfo.Size
NameSpace.ReadDir.RangeStmt_10514.i
NameSpace.Fprint.ns
NameSpace.ReadDir.readDirErr
NameSpace.ReadDir.RangeStmt_9821.BlockStmt.BlockStmt.RangeStmt_10044.f
NameSpace.Fprint
NameSpace.Fprint.RangeStmt_4887.mtpt
NameSpace.Open.RangeStmt_7131.BlockStmt.r
NameSpace.Stat.path
dirInfo.IsDir.d
NameSpace.ReadDir.ns
NameSpace.ReadDir.path
mountedFS
mountedFS.new
NameSpace.Open.err
dirInfo
mountedFS.translate.path
NameSpace.Fprint.w
NameSpace.ReadDir.all
byName.Less
byName.Swap.i
mountedFS.old
NameSpace.Bind.m
NameSpace.clean
NameSpace.Open.RangeStmt_7131.BlockStmt.tp
NameSpace.Bind.new
NameSpace.ReadDir.RangeStmt_10514.BlockStmt.RangeStmt_10543.BlockStmt.name
dirInfo.Name
Members
X