platform_build_blueprint/pathtools/fs.go
Colin Cross 2f95ec7031 Fix reading absolute paths through OsFs
OsFs may be asked to read absolute paths if buildDir is absolute.
Check if the path is absolute before prepending srcDir to it.

Bug: 146437378
Test: fs_test.go
Change-Id: I2a67593e9d836ca3e11dc10b81f49a4fb49d2cdf
2020-01-10 13:52:22 -08:00

565 lines
13 KiB
Go

// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pathtools
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
)
// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
type ShouldFollowSymlinks bool
const (
FollowSymlinks = ShouldFollowSymlinks(true)
DontFollowSymlinks = ShouldFollowSymlinks(false)
)
var OsFs FileSystem = &osFs{}
func MockFs(files map[string][]byte) FileSystem {
fs := &mockFs{
files: make(map[string][]byte, len(files)),
dirs: make(map[string]bool),
symlinks: make(map[string]string),
all: []string(nil),
}
for f, b := range files {
if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
continue
}
fs.files[filepath.Clean(f)] = b
dir := filepath.Dir(f)
for dir != "." && dir != "/" {
fs.dirs[dir] = true
dir = filepath.Dir(dir)
}
fs.dirs[dir] = true
}
fs.dirs["."] = true
fs.dirs["/"] = true
for f := range fs.files {
fs.all = append(fs.all, f)
}
for d := range fs.dirs {
fs.all = append(fs.all, d)
}
for s := range fs.symlinks {
fs.all = append(fs.all, s)
}
sort.Strings(fs.all)
return fs
}
type ReaderAtSeekerCloser interface {
io.Reader
io.ReaderAt
io.Seeker
io.Closer
}
type FileSystem interface {
// Open opens a file for reading. Follows symlinks.
Open(name string) (ReaderAtSeekerCloser, error)
// Exists returns whether the file exists and whether it is a directory. Follows symlinks.
Exists(name string) (bool, bool, error)
Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error)
glob(pattern string) (matches []string, err error)
// IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks.
// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
IsDir(name string) (bool, error)
// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
// not exist. Returns os.ErrNotExist if the path does not exist.
IsSymlink(name string) (bool, error)
// Lstat returns info on a file without following symlinks.
Lstat(name string) (os.FileInfo, error)
// Lstat returns info on a file.
Stat(name string) (os.FileInfo, error)
// ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)
// ReadDirNames returns a list of everything in a directory.
ReadDirNames(name string) ([]string, error)
// Readlink returns the destination of the named symbolic link.
Readlink(name string) (string, error)
}
// osFs implements FileSystem using the local disk.
type osFs struct {
srcDir string
}
func NewOsFs(path string) FileSystem {
return &osFs{srcDir: path}
}
func (fs *osFs) toAbs(path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(fs.srcDir, path)
}
func (fs *osFs) removeSrcDirPrefix(path string) string {
if fs.srcDir == "" {
return path
}
rel, err := filepath.Rel(fs.srcDir, path)
if err != nil {
panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
fs.srcDir, path, err))
}
if strings.HasPrefix(rel, "../") {
panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
fs.srcDir, path, rel))
}
return rel
}
func (fs *osFs) removeSrcDirPrefixes(paths []string) []string {
if fs.srcDir != "" {
for i, path := range paths {
paths[i] = fs.removeSrcDirPrefix(path)
}
}
return paths
}
func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
return os.Open(fs.toAbs(name))
}
func (fs *osFs) Exists(name string) (bool, bool, error) {
stat, err := os.Stat(fs.toAbs(name))
if err == nil {
return true, stat.IsDir(), nil
} else if os.IsNotExist(err) {
return false, false, nil
} else {
return false, false, err
}
}
func (fs *osFs) IsDir(name string) (bool, error) {
info, err := os.Stat(fs.toAbs(name))
if err != nil {
return false, err
}
return info.IsDir(), nil
}
func (fs *osFs) IsSymlink(name string) (bool, error) {
if info, err := os.Lstat(fs.toAbs(name)); err != nil {
return false, err
} else {
return info.Mode()&os.ModeSymlink != 0, nil
}
}
func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
return startGlob(fs, pattern, excludes, follow)
}
func (fs *osFs) glob(pattern string) ([]string, error) {
paths, err := filepath.Glob(fs.toAbs(pattern))
fs.removeSrcDirPrefixes(paths)
return paths, err
}
func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
return os.Lstat(fs.toAbs(path))
}
func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
return os.Stat(fs.toAbs(path))
}
// Returns a list of all directories under dir
func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
return listDirsRecursive(fs, name, follow)
}
func (fs *osFs) ReadDirNames(name string) ([]string, error) {
dir, err := os.Open(fs.toAbs(name))
if err != nil {
return nil, err
}
defer dir.Close()
contents, err := dir.Readdirnames(-1)
if err != nil {
return nil, err
}
sort.Strings(contents)
return contents, nil
}
func (fs *osFs) Readlink(name string) (string, error) {
return os.Readlink(fs.toAbs(name))
}
type mockFs struct {
files map[string][]byte
dirs map[string]bool
symlinks map[string]string
all []string
}
func (m *mockFs) followSymlinks(name string) string {
dir, file := saneSplit(name)
if dir != "." && dir != "/" {
dir = m.followSymlinks(dir)
}
name = filepath.Join(dir, file)
for i := 0; i < 255; i++ {
i++
if i > 255 {
panic("symlink loop")
}
to, exists := m.symlinks[name]
if !exists {
break
}
if filepath.IsAbs(to) {
name = to
} else {
name = filepath.Join(dir, to)
}
}
return name
}
func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) {
name = filepath.Clean(name)
name = m.followSymlinks(name)
if f, ok := m.files[name]; ok {
return struct {
io.Closer
*bytes.Reader
}{
ioutil.NopCloser(nil),
bytes.NewReader(f),
}, nil
}
return nil, &os.PathError{
Op: "open",
Path: name,
Err: os.ErrNotExist,
}
}
func (m *mockFs) Exists(name string) (bool, bool, error) {
name = filepath.Clean(name)
name = m.followSymlinks(name)
if _, ok := m.files[name]; ok {
return ok, false, nil
}
if _, ok := m.dirs[name]; ok {
return ok, true, nil
}
return false, false, nil
}
func (m *mockFs) IsDir(name string) (bool, error) {
dir := filepath.Dir(name)
if dir != "." && dir != "/" {
isDir, err := m.IsDir(dir)
if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
isDir = false
} else if err != nil {
return false, err
}
if !isDir {
return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
}
}
name = filepath.Clean(name)
name = m.followSymlinks(name)
if _, ok := m.dirs[name]; ok {
return true, nil
}
if _, ok := m.files[name]; ok {
return false, nil
}
return false, os.ErrNotExist
}
func (m *mockFs) IsSymlink(name string) (bool, error) {
dir, file := saneSplit(name)
dir = m.followSymlinks(dir)
name = filepath.Join(dir, file)
if _, isSymlink := m.symlinks[name]; isSymlink {
return true, nil
}
if _, isDir := m.dirs[name]; isDir {
return false, nil
}
if _, isFile := m.files[name]; isFile {
return false, nil
}
return false, os.ErrNotExist
}
func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
return startGlob(m, pattern, excludes, follow)
}
func unescapeGlob(s string) string {
i := 0
for i < len(s) {
if s[i] == '\\' {
s = s[:i] + s[i+1:]
} else {
i++
}
}
return s
}
func (m *mockFs) glob(pattern string) ([]string, error) {
dir, file := saneSplit(pattern)
dir = unescapeGlob(dir)
toDir := m.followSymlinks(dir)
var matches []string
for _, f := range m.all {
fDir, fFile := saneSplit(f)
if toDir == fDir {
match, err := filepath.Match(file, fFile)
if err != nil {
return nil, err
}
if (f == "." || f == "/") && f != pattern {
// filepath.Glob won't return "." or "/" unless the pattern was "." or "/"
match = false
}
if match {
matches = append(matches, filepath.Join(dir, fFile))
}
}
}
return matches, nil
}
type mockStat struct {
name string
size int64
mode os.FileMode
}
func (ms *mockStat) Name() string { return ms.name }
func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() }
func (ms *mockStat) Size() int64 { return ms.size }
func (ms *mockStat) Mode() os.FileMode { return ms.mode }
func (ms *mockStat) ModTime() time.Time { return time.Time{} }
func (ms *mockStat) Sys() interface{} { return nil }
func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
dir, file := saneSplit(name)
dir = m.followSymlinks(dir)
name = filepath.Join(dir, file)
ms := mockStat{
name: file,
}
if symlink, isSymlink := m.symlinks[name]; isSymlink {
ms.mode = os.ModeSymlink
ms.size = int64(len(symlink))
} else if _, isDir := m.dirs[name]; isDir {
ms.mode = os.ModeDir
} else if _, isFile := m.files[name]; isFile {
ms.mode = 0
ms.size = int64(len(m.files[name]))
} else {
return nil, os.ErrNotExist
}
return &ms, nil
}
func (m *mockFs) Stat(name string) (os.FileInfo, error) {
name = filepath.Clean(name)
origName := name
name = m.followSymlinks(name)
ms := mockStat{
name: filepath.Base(origName),
size: int64(len(m.files[name])),
}
if _, isDir := m.dirs[name]; isDir {
ms.mode = os.ModeDir
} else if _, isFile := m.files[name]; isFile {
ms.mode = 0
ms.size = int64(len(m.files[name]))
} else {
return nil, os.ErrNotExist
}
return &ms, nil
}
func (m *mockFs) ReadDirNames(name string) ([]string, error) {
name = filepath.Clean(name)
name = m.followSymlinks(name)
exists, isDir, err := m.Exists(name)
if err != nil {
return nil, err
}
if !exists {
return nil, os.ErrNotExist
}
if !isDir {
return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
}
var ret []string
for _, f := range m.all {
dir, file := saneSplit(f)
if dir == name && len(file) > 0 && file[0] != '.' {
ret = append(ret, file)
}
}
return ret, nil
}
func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
return listDirsRecursive(m, name, follow)
}
func (m *mockFs) Readlink(name string) (string, error) {
dir, file := saneSplit(name)
dir = m.followSymlinks(dir)
origName := name
name = filepath.Join(dir, file)
if dest, isSymlink := m.symlinks[name]; isSymlink {
return dest, nil
}
if exists, _, err := m.Exists(name); err != nil {
return "", err
} else if !exists {
return "", os.ErrNotExist
} else {
return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL)
}
}
func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
name = filepath.Clean(name)
isDir, err := fs.IsDir(name)
if err != nil {
return nil, err
}
if !isDir {
return nil, nil
}
dirs := []string{name}
subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
if err != nil {
return nil, err
}
for _, d := range subDirs {
dirs = append(dirs, filepath.Join(name, d))
}
return dirs, nil
}
func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
depth++
if depth > 255 {
return nil, fmt.Errorf("too many symlinks")
}
contents, err := fs.ReadDirNames(name)
if err != nil {
return nil, err
}
var dirs []string
for _, f := range contents {
if f[0] == '.' {
continue
}
f = filepath.Join(name, f)
if isSymlink, _ := fs.IsSymlink(f); isSymlink && follow == DontFollowSymlinks {
continue
}
if isDir, _ := fs.IsDir(f); isDir {
dirs = append(dirs, f)
subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
if err != nil {
return nil, err
}
for _, s := range subDirs {
dirs = append(dirs, filepath.Join(f, s))
}
}
}
for i, d := range dirs {
rel, err := filepath.Rel(name, d)
if err != nil {
return nil, err
}
dirs[i] = rel
}
return dirs, nil
}