1// Copyright 2013 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	"go/build"
10	"io/ioutil"
11	"os"
12	pathpkg "path"
13	"path/filepath"
14	"runtime"
15)
16
17// We expose a new variable because otherwise we need to copy the findGOROOT logic again
18// from cmd/godoc which is already copied twice from the standard library.
19
20// GOROOT is the GOROOT path under which the godoc binary is running.
21// It is needed to check whether a filesystem root is under GOROOT or not.
22// This is set from cmd/godoc/main.go.
23var GOROOT = runtime.GOROOT()
24
25// OS returns an implementation of FileSystem reading from the
26// tree rooted at root.  Recording a root is convenient everywhere
27// but necessary on Windows, because the slash-separated path
28// passed to Open has no way to specify a drive letter.  Using a root
29// lets code refer to OS(`c:\`), OS(`d:\`) and so on.
30func OS(root string) FileSystem {
31	var t RootType
32	switch {
33	case root == GOROOT:
34		t = RootTypeGoRoot
35	case isGoPath(root):
36		t = RootTypeGoPath
37	}
38	return osFS{rootPath: root, rootType: t}
39}
40
41type osFS struct {
42	rootPath string
43	rootType RootType
44}
45
46func isGoPath(path string) bool {
47	for _, bp := range filepath.SplitList(build.Default.GOPATH) {
48		for _, gp := range filepath.SplitList(path) {
49			if bp == gp {
50				return true
51			}
52		}
53	}
54	return false
55}
56
57func (root osFS) String() string { return "os(" + root.rootPath + ")" }
58
59// RootType returns the root type for the filesystem.
60//
61// Note that we ignore the path argument because roottype is a property of
62// this filesystem. But for other filesystems, the roottype might need to be
63// dynamically deduced at call time.
64func (root osFS) RootType(path string) RootType {
65	return root.rootType
66}
67
68func (root osFS) resolve(path string) string {
69	// Clean the path so that it cannot possibly begin with ../.
70	// If it did, the result of filepath.Join would be outside the
71	// tree rooted at root.  We probably won't ever see a path
72	// with .. in it, but be safe anyway.
73	path = pathpkg.Clean("/" + path)
74
75	return filepath.Join(root.rootPath, path)
76}
77
78func (root osFS) Open(path string) (ReadSeekCloser, error) {
79	f, err := os.Open(root.resolve(path))
80	if err != nil {
81		return nil, err
82	}
83	fi, err := f.Stat()
84	if err != nil {
85		f.Close()
86		return nil, err
87	}
88	if fi.IsDir() {
89		f.Close()
90		return nil, fmt.Errorf("Open: %s is a directory", path)
91	}
92	return f, nil
93}
94
95func (root osFS) Lstat(path string) (os.FileInfo, error) {
96	return os.Lstat(root.resolve(path))
97}
98
99func (root osFS) Stat(path string) (os.FileInfo, error) {
100	return os.Stat(root.resolve(path))
101}
102
103func (root osFS) ReadDir(path string) ([]os.FileInfo, error) {
104	return ioutil.ReadDir(root.resolve(path)) // is sorted
105}
106