You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
11 KiB

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