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