diff --git a/godoc/vfs/emptyvfs.go b/godoc/vfs/emptyvfs.go new file mode 100644 index 0000000000..01b6942f0a --- /dev/null +++ b/godoc/vfs/emptyvfs.go @@ -0,0 +1,85 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vfs + +import ( + "fmt" + "os" + "time" +) + +// NewNameSpace returns a NameSpace pre-initialized with an empty +// emulated directory mounted on the root mount point "/". This +// allows directory traversal routines to work properly even if +// a folder is not explicitly mounted at root by the user. +func NewNameSpace() NameSpace { + ns := NameSpace{} + ns.Bind("/", &emptyVFS{}, "/", BindReplace) + return ns +} + +// type emptyVFS emulates a FileSystem consisting of an empty directory +type emptyVFS struct{} + +// Open implements Opener. Since emptyVFS is an empty directory, all +// attempts to open a file should returns errors. +func (e *emptyVFS) Open(path string) (ReadSeekCloser, error) { + if path == "/" { + return nil, fmt.Errorf("open: / is a directory") + } + return nil, os.ErrNotExist +} + +// Stat returns os.FileInfo for an empty directory if the path is +// is root "/" or error. os.FileInfo is implemented by emptyVFS +func (e *emptyVFS) Stat(path string) (os.FileInfo, error) { + if path == "/" { + return e, nil + } + return nil, os.ErrNotExist +} + +func (e *emptyVFS) Lstat(path string) (os.FileInfo, error) { + return e.Stat(path) +} + +// ReadDir returns an empty os.FileInfo slice for "/", else error. +func (e *emptyVFS) ReadDir(path string) ([]os.FileInfo, error) { + if path == "/" { + return []os.FileInfo{}, nil + } + return nil, os.ErrNotExist +} + +func (e *emptyVFS) String() string { + return "emptyVFS(/)" +} + +// These functions below implement os.FileInfo for the single +// empty emulated directory. + +func (e *emptyVFS) Name() string { + return "/" +} + +func (e *emptyVFS) Size() int64 { + return 0 +} + +func (e *emptyVFS) Mode() os.FileMode { + return os.ModeDir | os.ModePerm +} + +func (e *emptyVFS) ModTime() time.Time { + return time.Time{} +} + +func (e *emptyVFS) IsDir() bool { + return true +} + +func (e *emptyVFS) Sys() interface{} { + return nil +} diff --git a/godoc/vfs/emptyvfs_test.go b/godoc/vfs/emptyvfs_test.go new file mode 100644 index 0000000000..2d8d72e6ba --- /dev/null +++ b/godoc/vfs/emptyvfs_test.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package vfs_test + +import ( + "golang.org/x/tools/godoc/vfs" + "golang.org/x/tools/godoc/vfs/mapfs" + "testing" + "time" +) + +func TestNewNameSpace(t *testing.T) { + + // We will mount this filesystem under /fs1 + mount := mapfs.New(map[string]string{"fs1file": "abcdefgh"}) + + // Existing process. This should give error on Stat("/") + t1 := vfs.NameSpace{} + t1.Bind("/fs1", mount, "/", vfs.BindReplace) + + // using NewNameSpace. This should work fine. + t2 := vfs.NewNameSpace() + t2.Bind("/fs1", mount, "/", vfs.BindReplace) + + testcases := map[string][]bool{ + "/": []bool{false, true}, + "/fs1": []bool{true, true}, + "/fs1/fs1file": []bool{true, true}, + } + + fss := []vfs.FileSystem{t1, t2} + + for j, fs := range fss { + for k, v := range testcases { + _, err := fs.Stat(k) + result := err == nil + if result != v[j] { + t.Errorf("fs: %d, testcase: %s, want: %v, got: %v, err: %s", j, k, v[j], result, err) + } + } + } + + fi, err := t2.Stat("/") + if err != nil { + t.Fatal(err) + } + + if fi.Name() != "/" { + t.Errorf("t2.Name() : want:%s got:%s", "/", fi.Name()) + } + + if !fi.ModTime().IsZero() { + t.Errorf("t2.Modime() : want:%v got:%v", time.Time{}, fi.ModTime()) + } +} diff --git a/godoc/vfs/namespace.go b/godoc/vfs/namespace.go index dbba20cb7c..5a31fe61ea 100644 --- a/godoc/vfs/namespace.go +++ b/godoc/vfs/namespace.go @@ -53,6 +53,11 @@ const debugNS = false // operate on a path beginning with old, replace that prefix (old) with new // and then pass that path to the FileSystem implementation fs. // +// If you do not explicitly mount a FileSystem at the root mountpoint "/" of the +// NameSpace like above, Stat("/") will return a "not found" error which could +// break typical directory traversal routines. In such cases, use NewNameSpace() +// to get a NameSpace pre-initialized with an emulated empty directory at root. +// // Given this name space, a ReadDir of /src/pkg/code will check each prefix // of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src, // then /), stopping when it finds one. For the above example, /src/pkg/code