github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/cue/load/fs.go (about)

     1  // Copyright 2018 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package load
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/joomcode/cue/cue/ast"
    28  	"github.com/joomcode/cue/cue/errors"
    29  	"github.com/joomcode/cue/cue/token"
    30  )
    31  
    32  type overlayFile struct {
    33  	basename string
    34  	contents []byte
    35  	file     *ast.File
    36  	modtime  time.Time
    37  	isDir    bool
    38  }
    39  
    40  func (f *overlayFile) Name() string       { return f.basename }
    41  func (f *overlayFile) Size() int64        { return int64(len(f.contents)) }
    42  func (f *overlayFile) Mode() os.FileMode  { return 0644 }
    43  func (f *overlayFile) ModTime() time.Time { return f.modtime }
    44  func (f *overlayFile) IsDir() bool        { return f.isDir }
    45  func (f *overlayFile) Sys() interface{}   { return nil }
    46  
    47  // A fileSystem specifies the supporting context for a build.
    48  type fileSystem struct {
    49  	overlayDirs map[string]map[string]*overlayFile
    50  	cwd         string
    51  }
    52  
    53  func (fs *fileSystem) getDir(dir string, create bool) map[string]*overlayFile {
    54  	dir = filepath.Clean(dir)
    55  	m, ok := fs.overlayDirs[dir]
    56  	if !ok && create {
    57  		m = map[string]*overlayFile{}
    58  		fs.overlayDirs[dir] = m
    59  	}
    60  	return m
    61  }
    62  
    63  func (fs *fileSystem) init(c *Config) error {
    64  	fs.cwd = c.Dir
    65  
    66  	overlay := c.Overlay
    67  	fs.overlayDirs = map[string]map[string]*overlayFile{}
    68  
    69  	// Organize overlay
    70  	for filename, src := range overlay {
    71  		// TODO: do we need to further clean the path or check that the
    72  		// specified files are within the root/ absolute files?
    73  		dir, base := filepath.Split(filename)
    74  		m := fs.getDir(dir, true)
    75  
    76  		b, file, err := src.contents()
    77  		if err != nil {
    78  			return err
    79  		}
    80  		m[base] = &overlayFile{
    81  			basename: base,
    82  			contents: b,
    83  			file:     file,
    84  			modtime:  time.Now(),
    85  		}
    86  
    87  		for {
    88  			prevdir := dir
    89  			dir, base = filepath.Split(filepath.Dir(dir))
    90  			if dir == prevdir || dir == "" {
    91  				break
    92  			}
    93  			m := fs.getDir(dir, true)
    94  			if m[base] == nil {
    95  				m[base] = &overlayFile{
    96  					basename: base,
    97  					modtime:  time.Now(),
    98  					isDir:    true,
    99  				}
   100  			}
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  func (fs *fileSystem) joinPath(elem ...string) string {
   107  	return filepath.Join(elem...)
   108  }
   109  
   110  func (fs *fileSystem) splitPathList(s string) []string {
   111  	return filepath.SplitList(s)
   112  }
   113  
   114  func (fs *fileSystem) isAbsPath(path string) bool {
   115  	return filepath.IsAbs(path)
   116  }
   117  
   118  func (fs *fileSystem) makeAbs(path string) string {
   119  	if fs.isAbsPath(path) {
   120  		return path
   121  	}
   122  	return filepath.Clean(filepath.Join(fs.cwd, path))
   123  }
   124  
   125  func (fs *fileSystem) isDir(path string) bool {
   126  	path = fs.makeAbs(path)
   127  	if fs.getDir(path, false) != nil {
   128  		return true
   129  	}
   130  	fi, err := os.Stat(path)
   131  	return err == nil && fi.IsDir()
   132  }
   133  
   134  func (fs *fileSystem) hasSubdir(root, dir string) (rel string, ok bool) {
   135  	// Try using paths we received.
   136  	if rel, ok = hasSubdir(root, dir); ok {
   137  		return
   138  	}
   139  
   140  	// Try expanding symlinks and comparing
   141  	// expanded against unexpanded and
   142  	// expanded against expanded.
   143  	rootSym, _ := filepath.EvalSymlinks(root)
   144  	dirSym, _ := filepath.EvalSymlinks(dir)
   145  
   146  	if rel, ok = hasSubdir(rootSym, dir); ok {
   147  		return
   148  	}
   149  	if rel, ok = hasSubdir(root, dirSym); ok {
   150  		return
   151  	}
   152  	return hasSubdir(rootSym, dirSym)
   153  }
   154  
   155  func hasSubdir(root, dir string) (rel string, ok bool) {
   156  	const sep = string(filepath.Separator)
   157  	root = filepath.Clean(root)
   158  	if !strings.HasSuffix(root, sep) {
   159  		root += sep
   160  	}
   161  	dir = filepath.Clean(dir)
   162  	if !strings.HasPrefix(dir, root) {
   163  		return "", false
   164  	}
   165  	return filepath.ToSlash(dir[len(root):]), true
   166  }
   167  
   168  func (fs *fileSystem) readDir(path string) ([]os.FileInfo, errors.Error) {
   169  	path = fs.makeAbs(path)
   170  	m := fs.getDir(path, false)
   171  	items, err := ioutil.ReadDir(path)
   172  	if err != nil {
   173  		if !os.IsNotExist(err) || m == nil {
   174  			return nil, errors.Wrapf(err, token.NoPos, "readDir")
   175  		}
   176  	}
   177  	if m != nil {
   178  		done := map[string]bool{}
   179  		for i, fi := range items {
   180  			done[fi.Name()] = true
   181  			if o := m[fi.Name()]; o != nil {
   182  				items[i] = o
   183  			}
   184  		}
   185  		for _, o := range m {
   186  			if !done[o.Name()] {
   187  				items = append(items, o)
   188  			}
   189  		}
   190  		sort.Slice(items, func(i, j int) bool {
   191  			return items[i].Name() < items[j].Name()
   192  		})
   193  	}
   194  	return items, nil
   195  }
   196  
   197  func (fs *fileSystem) getOverlay(path string) *overlayFile {
   198  	dir, base := filepath.Split(path)
   199  	if m := fs.getDir(dir, false); m != nil {
   200  		return m[base]
   201  	}
   202  	return nil
   203  }
   204  
   205  func (fs *fileSystem) stat(path string) (os.FileInfo, errors.Error) {
   206  	path = fs.makeAbs(path)
   207  	if fi := fs.getOverlay(path); fi != nil {
   208  		return fi, nil
   209  	}
   210  	fi, err := os.Stat(path)
   211  	if err != nil {
   212  		return nil, errors.Wrapf(err, token.NoPos, "stat")
   213  	}
   214  	return fi, nil
   215  }
   216  
   217  func (fs *fileSystem) lstat(path string) (os.FileInfo, errors.Error) {
   218  	path = fs.makeAbs(path)
   219  	if fi := fs.getOverlay(path); fi != nil {
   220  		return fi, nil
   221  	}
   222  	fi, err := os.Lstat(path)
   223  	if err != nil {
   224  		return nil, errors.Wrapf(err, token.NoPos, "stat")
   225  	}
   226  	return fi, nil
   227  }
   228  
   229  func (fs *fileSystem) openFile(path string) (io.ReadCloser, errors.Error) {
   230  	path = fs.makeAbs(path)
   231  	if fi := fs.getOverlay(path); fi != nil {
   232  		return ioutil.NopCloser(bytes.NewReader(fi.contents)), nil
   233  	}
   234  
   235  	f, err := os.Open(path)
   236  	if err != nil {
   237  		return nil, errors.Wrapf(err, token.NoPos, "load")
   238  	}
   239  	return f, nil
   240  }
   241  
   242  var skipDir = errors.Newf(token.NoPos, "skip directory")
   243  
   244  type walkFunc func(path string, info os.FileInfo, err errors.Error) errors.Error
   245  
   246  func (fs *fileSystem) walk(root string, f walkFunc) error {
   247  	fi, err := fs.lstat(root)
   248  	if err != nil {
   249  		err = f(root, fi, err)
   250  	} else if !fi.IsDir() {
   251  		return errors.Newf(token.NoPos, "path %q is not a directory", root)
   252  	} else {
   253  		err = fs.walkRec(root, fi, f)
   254  	}
   255  	if err == skipDir {
   256  		return nil
   257  	}
   258  	return err
   259  
   260  }
   261  
   262  func (fs *fileSystem) walkRec(path string, info os.FileInfo, f walkFunc) errors.Error {
   263  	if !info.IsDir() {
   264  		return f(path, info, nil)
   265  	}
   266  
   267  	dir, err := fs.readDir(path)
   268  	err1 := f(path, info, err)
   269  
   270  	// If err != nil, walk can't walk into this directory.
   271  	// err1 != nil means walkFn want walk to skip this directory or stop walking.
   272  	// Therefore, if one of err and err1 isn't nil, walk will return.
   273  	if err != nil || err1 != nil {
   274  		// The caller's behavior is controlled by the return value, which is decided
   275  		// by walkFn. walkFn may ignore err and return nil.
   276  		// If walkFn returns SkipDir, it will be handled by the caller.
   277  		// So walk should return whatever walkFn returns.
   278  		return err1
   279  	}
   280  
   281  	for _, info := range dir {
   282  		filename := fs.joinPath(path, info.Name())
   283  		err = fs.walkRec(filename, info, f)
   284  		if err != nil {
   285  			if !info.IsDir() || err != skipDir {
   286  				return err
   287  			}
   288  		}
   289  	}
   290  	return nil
   291  }