kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/vfs/vfs.go (about)

     1  /*
     2   * Copyright 2015 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Package vfs defines a generic file system interface commonly used by Kythe
    18  // libraries.
    19  package vfs // import "kythe.io/kythe/go/platform/vfs"
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"regexp"
    29  )
    30  
    31  // globPattern is used by globWalker to escape glob special characters.
    32  var globPattern = regexp.MustCompile(`[[*?\\]`)
    33  
    34  // ErrNotSupported is returned for all unsupported VFS operations.
    35  var ErrNotSupported = errors.New("operation not supported")
    36  
    37  // Interface is a virtual file system interface for reading and writing files.
    38  // It is used to wrap the normal os package functions so that other file storage
    39  // implementations be used in lieu.  For instance, there could be
    40  // implementations for cloud storage back-ends or databases.  Depending on the
    41  // implementation, the Writer methods can be unsupported and always return
    42  // ErrNotSupported.
    43  type Interface interface {
    44  	Reader
    45  	Writer
    46  }
    47  
    48  // TempFile composes io.WriteCloser and access to its "name". For
    49  // file-based implementations, this should be the full path to the full.
    50  type TempFile interface {
    51  	io.WriteCloser
    52  	Name() string
    53  }
    54  
    55  // Reader is a virtual file system interface for reading files.
    56  type Reader interface {
    57  	// Stat returns file status information for path, as os.Stat.
    58  	Stat(ctx context.Context, path string) (os.FileInfo, error)
    59  
    60  	// Open opens an existing file for reading, as os.Open.
    61  	Open(ctx context.Context, path string) (FileReader, error)
    62  
    63  	// Glob returns all the paths matching the specified glob pattern, as
    64  	// filepath.Glob.
    65  	Glob(ctx context.Context, glob string) ([]string, error)
    66  }
    67  
    68  // Walker is a virtual file system interface for traversing directories.
    69  type Walker interface {
    70  	// Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root.
    71  	// See filepath.Walk for more details.
    72  	Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error
    73  }
    74  
    75  // Writer is a virtual file system interface for writing files.
    76  type Writer interface {
    77  	// MkdirAll recursively creates the specified directory path with the given
    78  	// permissions, as os.MkdirAll.
    79  	MkdirAll(ctx context.Context, path string, mode os.FileMode) error
    80  
    81  	// Create creates a new file for writing, as os.Create.
    82  	Create(ctx context.Context, path string) (io.WriteCloser, error)
    83  
    84  	// CreateTempFile creates a new temp file returning a TempFile. The
    85  	// name of the file is constructed from dir pattern and per
    86  	// ioutil.TempFile:
    87  	// The filename is generated by taking pattern and adding a random
    88  	// string to the end. If pattern includes a "*", the random string
    89  	// replaces the last "*". If dir is the empty string, CreateTempFile
    90  	// uses an unspecified default directory.
    91  	CreateTempFile(ctx context.Context, dir, pattern string) (TempFile, error)
    92  
    93  	// Rename renames oldPath to newPath, as os.Rename, overwriting newPath if
    94  	// it exists.
    95  	Rename(ctx context.Context, oldPath, newPath string) error
    96  
    97  	// Remove deletes the file specified by path, as os.Remove.
    98  	Remove(ctx context.Context, path string) error
    99  }
   100  
   101  // FileReader composes interfaces from io that readable files from the vfs must
   102  // implement.
   103  type FileReader interface {
   104  	io.ReadCloser
   105  	io.ReaderAt
   106  	io.Seeker
   107  }
   108  
   109  // Default is the global default VFS used by Kythe libraries that wish to access
   110  // the file system.  This is usually the LocalFS and should only be changed in
   111  // very specialized cases (i.e. don't change it).
   112  var Default Interface = LocalFS{}
   113  
   114  // ReadFile is the equivalent of ioutil.ReadFile using the Default VFS.
   115  func ReadFile(ctx context.Context, filename string) ([]byte, error) {
   116  	f, err := Open(ctx, filename)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	defer f.Close() // ignore errors
   121  	return ioutil.ReadAll(f)
   122  }
   123  
   124  // Stat returns file status information for path, using the Default VFS.
   125  func Stat(ctx context.Context, path string) (os.FileInfo, error) { return Default.Stat(ctx, path) }
   126  
   127  // MkdirAll recursively creates the specified directory path with the given
   128  // permissions, using the Default VFS.
   129  func MkdirAll(ctx context.Context, path string, mode os.FileMode) error {
   130  	return Default.MkdirAll(ctx, path, mode)
   131  }
   132  
   133  // Open opens an existing file for reading, using the Default VFS.
   134  func Open(ctx context.Context, path string) (FileReader, error) { return Default.Open(ctx, path) }
   135  
   136  // Create creates a new file for writing, using the Default VFS.
   137  func Create(ctx context.Context, path string) (io.WriteCloser, error) {
   138  	return Default.Create(ctx, path)
   139  }
   140  
   141  // CreateTempFile creates a new TempFile, using the Default VFS.
   142  func CreateTempFile(ctx context.Context, dir, pattern string) (TempFile, error) {
   143  	return Default.CreateTempFile(ctx, dir, pattern)
   144  }
   145  
   146  // Rename renames oldPath to newPath, using the Default VFS, overwriting newPath
   147  // if it exists.
   148  func Rename(ctx context.Context, oldPath, newPath string) error {
   149  	return Default.Rename(ctx, oldPath, newPath)
   150  }
   151  
   152  // Remove deletes the file specified by path, using the Default VFS.
   153  func Remove(ctx context.Context, path string) error { return Default.Remove(ctx, path) }
   154  
   155  // Glob returns all the paths matching the specified glob pattern, using the
   156  // Default VFS.
   157  func Glob(ctx context.Context, glob string) ([]string, error) { return Default.Glob(ctx, glob) }
   158  
   159  // Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root.
   160  // See filepath.Walk for more details.
   161  func Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error {
   162  	return NewWalker(Default).Walk(ctx, root, walkFn)
   163  }
   164  
   165  // NewWalker returns a Walker instance over the provided reader.
   166  func NewWalker(r Reader) Walker {
   167  	w, ok := r.(Walker)
   168  	if ok {
   169  		return w
   170  	}
   171  	return &globWalker{r}
   172  }
   173  
   174  // LocalFS implements the VFS interface using the standard Go library.
   175  type LocalFS struct{}
   176  
   177  // Stat implements part of the VFS interface.
   178  func (LocalFS) Stat(_ context.Context, path string) (os.FileInfo, error) {
   179  	return os.Stat(path)
   180  }
   181  
   182  // MkdirAll implements part of the VFS interface.
   183  func (LocalFS) MkdirAll(_ context.Context, path string, mode os.FileMode) error {
   184  	return os.MkdirAll(path, mode)
   185  }
   186  
   187  // Open implements part of the VFS interface.
   188  func (LocalFS) Open(_ context.Context, path string) (FileReader, error) {
   189  	if path == "-" {
   190  		return stdinWrapper{os.Stdin}, nil
   191  	}
   192  	return os.Open(path)
   193  }
   194  
   195  // Create implements part of the VFS interface.
   196  func (LocalFS) Create(_ context.Context, path string) (io.WriteCloser, error) {
   197  	return os.Create(path)
   198  }
   199  
   200  // CreateTempFile implements part of the VFS interface.
   201  func (LocalFS) CreateTempFile(_ context.Context, dir, pattern string) (TempFile, error) {
   202  	return ioutil.TempFile(dir, pattern)
   203  }
   204  
   205  // Rename implements part of the VFS interface.
   206  func (LocalFS) Rename(_ context.Context, oldPath, newPath string) error {
   207  	return os.Rename(oldPath, newPath)
   208  }
   209  
   210  // Remove implements part of the VFS interface.
   211  func (LocalFS) Remove(_ context.Context, path string) error {
   212  	return os.Remove(path)
   213  }
   214  
   215  // Glob implements part of the VFS interface.
   216  func (LocalFS) Glob(_ context.Context, glob string) ([]string, error) {
   217  	return filepath.Glob(glob)
   218  }
   219  
   220  // Walk implements part of the VFS interface.
   221  func (LocalFS) Walk(_ context.Context, root string, walkFn filepath.WalkFunc) error {
   222  	return filepath.Walk(root, walkFn)
   223  }
   224  
   225  // UnsupportedWriter implements the Writer interface methods with stubs that
   226  // always return ErrNotSupported.
   227  type UnsupportedWriter struct{ Reader }
   228  
   229  // Create implements part of Writer interface.  It is not supported.
   230  func (UnsupportedWriter) Create(_ context.Context, _ string) (io.WriteCloser, error) {
   231  	return nil, ErrNotSupported
   232  }
   233  
   234  // CreateTempFile implements part of the VFS interface. It is not supported.
   235  func (UnsupportedWriter) CreateTempFile(_ context.Context, dir, pattern string) (TempFile, error) {
   236  	return nil, ErrNotSupported
   237  }
   238  
   239  // MkdirAll implements part of Writer interface.  It is not supported.
   240  func (UnsupportedWriter) MkdirAll(_ context.Context, _ string, _ os.FileMode) error {
   241  	return ErrNotSupported
   242  }
   243  
   244  // Rename implements part of Writer interface.  It is not supported.
   245  func (UnsupportedWriter) Rename(_ context.Context, _, _ string) error { return ErrNotSupported }
   246  
   247  // Remove implements part of Writer interface.  It is not supported.
   248  func (UnsupportedWriter) Remove(_ context.Context, _ string) error { return ErrNotSupported }
   249  
   250  // UnseekableFileReader implements the io.Seeker and io.ReaderAt at portion of
   251  // FileReader with stubs that always return ErrNotSupported.
   252  type UnseekableFileReader struct {
   253  	io.ReadCloser
   254  }
   255  
   256  // ReadAt implements io.ReaderAt interface. It is not supported.
   257  func (UnseekableFileReader) ReadAt([]byte, int64) (int, error) {
   258  	return 0, ErrNotSupported
   259  }
   260  
   261  // Seek implements io.Seeker interface. It is not supported.
   262  func (UnseekableFileReader) Seek(int64, int) (int64, error) {
   263  	return 0, ErrNotSupported
   264  }
   265  
   266  // stdinWrapper is similar in purpose to ioutil.NopCloser, but allows access to
   267  // other os.File methods that implement FileReader rather than restricting to
   268  // just ioutil.ReadCloser.
   269  type stdinWrapper struct {
   270  	*os.File
   271  }
   272  
   273  func (stdinWrapper) Close() error {
   274  	return nil
   275  }
   276  
   277  // globWalker wraps a Reader interface using Glob and Stat to implement Walk.
   278  type globWalker struct {
   279  	r Reader
   280  }
   281  
   282  // escapeGlob escapes glob special characters.
   283  func escapeGlob(path string) string {
   284  	return globPattern.ReplaceAllString(path, `\$0`)
   285  }
   286  
   287  // Walk implements the Walker interface by delegating to Glob and Stat.
   288  func (gw *globWalker) Walk(ctx context.Context, root string, walkFn filepath.WalkFunc) error {
   289  	info, err := gw.r.Stat(ctx, root)
   290  	if err != nil {
   291  		err = walkFn(root, nil, err)
   292  	} else {
   293  		err = gw.walk(ctx, root, info, walkFn)
   294  	}
   295  	if err == filepath.SkipDir {
   296  		return nil
   297  	}
   298  	return err
   299  
   300  }
   301  
   302  // walk recusively descends path using vfs.Glob, calling walkFn on the results.
   303  func (gw *globWalker) walk(ctx context.Context, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
   304  	if !info.IsDir() {
   305  		return walkFn(path, info, nil)
   306  	}
   307  
   308  	names, err := gw.r.Glob(ctx, filepath.Join(escapeGlob(path), "*"))
   309  	userErr := walkFn(path, info, err)
   310  	// If err != nil, walk can't walk into this directory.
   311  	// userErr != nil means walkFn want walk to skip this directory or stop walking.
   312  	// Therefore, if one of err and userErr isn't nil, walk will return.
   313  	if err != nil || userErr != nil {
   314  		// The caller's behavior is controlled by the return value, which is decided
   315  		// by walkFn. walkFn may ignore err and return nil.
   316  		// If walkFn returns SkipDir, it will be handled by the caller.
   317  		// So walk should return whatever walkFn returns.
   318  		return userErr
   319  	}
   320  	for _, name := range names {
   321  		fileInfo, err := gw.r.Stat(ctx, name)
   322  		if err != nil {
   323  			if err := walkFn(name, fileInfo, err); err != nil && err != filepath.SkipDir {
   324  				return err
   325  			}
   326  		} else if err := gw.walk(ctx, name, fileInfo, walkFn); err != nil {
   327  			if !fileInfo.IsDir() || err != filepath.SkipDir {
   328  				return err
   329  			}
   330  		}
   331  	}
   332  	return nil
   333  }