github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/internal/vfs/vfs.go (about)

     1  // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors.
     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 vfs
    16  
    17  import (
    18  	"io"
    19  	"os"
    20  	"path/filepath"
    21  	"syscall"
    22  
    23  	"github.com/cockroachdb/errors"
    24  	"github.com/cockroachdb/errors/oserror"
    25  )
    26  
    27  // File is a readable, writable sequence of bytes.
    28  //
    29  // Typically, it will be an *os.File, but test code may choose to substitute
    30  // memory-backed implementations.
    31  type File interface {
    32  	io.Closer
    33  	io.Reader
    34  	io.ReaderAt
    35  	io.Writer
    36  	io.Seeker
    37  	Stat() (os.FileInfo, error)
    38  	Sync() error
    39  }
    40  
    41  // OpenOption provide an interface to do work on file handles in the Open()
    42  // call.
    43  type OpenOption interface {
    44  	// Apply is called on the file handle after it's opened.
    45  	Apply(File)
    46  }
    47  
    48  // FS is a namespace for files.
    49  //
    50  // The names are filepath names: they may be / separated or \ separated,
    51  // depending on the underlying operating system.
    52  type FS interface {
    53  	// Create creates the named file for reading and writing. If a file
    54  	// already exists at the provided name, it's removed first ensuring the
    55  	// resulting file descriptor points to a new inode.
    56  	Create(name string) (File, error)
    57  
    58  	// Link creates newname as a hard link to the oldname file.
    59  	Link(oldname, newname string) error
    60  
    61  	// Open opens the named file for reading. openOptions provides
    62  	Open(name string, opts ...OpenOption) (File, error)
    63  
    64  	// OpenDir opens the named directory for syncing.
    65  	OpenDir(name string) (File, error)
    66  
    67  	// Remove removes the named file or directory.
    68  	Remove(name string) error
    69  
    70  	// RemoveAll removes the named file or directory and any children it
    71  	// contains. It removes everything it can but returns the first error it
    72  	// encounters.
    73  	RemoveAll(name string) error
    74  
    75  	// Rename renames a file. It overwrites the file at newname if one exists,
    76  	// the same as os.Rename.
    77  	Rename(oldname, newname string) error
    78  
    79  	// ReuseForWrite attempts to reuse the file with oldname by renaming it to newname and opening
    80  	// it for writing without truncation. It is acceptable for the implementation to choose not
    81  	// to reuse oldname, and simply create the file with newname -- in this case the implementation
    82  	// should delete oldname. If the caller calls this function with an oldname that does not exist,
    83  	// the implementation may return an error.
    84  	ReuseForWrite(oldname, newname string) (File, error)
    85  
    86  	OpenForWrite(name string) (File, error)
    87  
    88  	OpenWR(name string) (File, error)
    89  
    90  	// MkdirAll creates a directory and all necessary parents. The permission
    91  	// bits perm have the same semantics as in os.MkdirAll. If the directory
    92  	// already exists, MkdirAll does nothing and returns nil.
    93  	MkdirAll(dir string, perm os.FileMode) error
    94  
    95  	// Lock locks the given file, creating the file if necessary, and
    96  	// truncating the file if it already exists. The lock is an exclusive lock
    97  	// (a write lock), but locked files should neither be read from nor written
    98  	// to. Such files should have zero size and only exist to co-ordinate
    99  	// ownership across processes.
   100  	//
   101  	// A nil Closer is returned if an error occurred. Otherwise, close that
   102  	// Closer to release the lock.
   103  	//
   104  	// On Linux and OSX, a lock has the same semantics as fcntl(2)'s advisory
   105  	// locks. In particular, closing any other file descriptor for the same
   106  	// file will release the lock prematurely.
   107  	//
   108  	// Attempting to lock a file that is already locked by the current process
   109  	// returns an error and leaves the existing lock untouched.
   110  	//
   111  	// Lock is not yet implemented on other operating systems, and calling it
   112  	// will return an error.
   113  	Lock(name string) (io.Closer, error)
   114  
   115  	// List returns a listing of the given directory. The names returned are
   116  	// relative to dir.
   117  	List(dir string) ([]string, error)
   118  
   119  	// Stat returns an os.FileInfo describing the named file.
   120  	Stat(name string) (os.FileInfo, error)
   121  
   122  	// PathBase returns the last element of path. Trailing path separators are
   123  	// removed before extracting the last element. If the path is empty, PathBase
   124  	// returns ".".  If the path consists entirely of separators, PathBase returns a
   125  	// single separator.
   126  	PathBase(path string) string
   127  
   128  	// PathJoin joins any number of path elements into a single path, adding a
   129  	// separator if necessary.
   130  	PathJoin(elem ...string) string
   131  
   132  	// PathDir returns all but the last element of path, typically the path's directory.
   133  	PathDir(path string) string
   134  
   135  	// GetDiskUsage returns disk space statistics for the filesystem where
   136  	// path is any file or directory within that filesystem.
   137  	GetDiskUsage(path string) (DiskUsage, error)
   138  }
   139  
   140  // DiskUsage summarizes disk space usage on a filesystem.
   141  type DiskUsage struct {
   142  	// Total disk space available to the current process in bytes.
   143  	AvailBytes uint64
   144  	// Total disk space in bytes.
   145  	TotalBytes uint64
   146  	// Used disk space in bytes.
   147  	UsedBytes uint64
   148  }
   149  
   150  // Default is a FS implementation backed by the underlying operating system's
   151  // file system.
   152  var Default FS = defaultFS{}
   153  
   154  type defaultFS struct{}
   155  
   156  func (defaultFS) Create(name string) (File, error) {
   157  	const openFlags = os.O_RDWR | os.O_CREATE | os.O_EXCL | syscall.O_CLOEXEC
   158  
   159  	f, err := os.OpenFile(name, openFlags, 0666)
   160  	// If the file already exists, remove it and try again.
   161  	//
   162  	// NB: We choose to remove the file instead of truncating it, despite the
   163  	// fact that we can't do so atomically, because it's more resistant to
   164  	// misuse when using hard links.
   165  
   166  	// We must loop in case another goroutine/thread/process is also
   167  	// attempting to create the a file at the same path.
   168  	for oserror.IsExist(err) {
   169  		if removeErr := os.Remove(name); removeErr != nil && !oserror.IsNotExist(removeErr) {
   170  			return f, errors.WithStack(removeErr)
   171  		}
   172  		f, err = os.OpenFile(name, openFlags, 0666)
   173  	}
   174  	return f, errors.WithStack(err)
   175  }
   176  
   177  func (defaultFS) Link(oldname, newname string) error {
   178  	return errors.WithStack(os.Link(oldname, newname))
   179  }
   180  
   181  func (defaultFS) Open(name string, opts ...OpenOption) (File, error) {
   182  	file, err := os.OpenFile(name, os.O_RDONLY|syscall.O_CLOEXEC, 0)
   183  	if err != nil {
   184  		return nil, errors.WithStack(err)
   185  	}
   186  	for _, opt := range opts {
   187  		opt.Apply(file)
   188  	}
   189  	return file, nil
   190  }
   191  
   192  func (defaultFS) Remove(name string) error {
   193  	return errors.WithStack(os.Remove(name))
   194  }
   195  
   196  func (defaultFS) RemoveAll(name string) error {
   197  	return errors.WithStack(os.RemoveAll(name))
   198  }
   199  
   200  func (defaultFS) Rename(oldname, newname string) error {
   201  	return errors.WithStack(os.Rename(oldname, newname))
   202  }
   203  
   204  func (fs defaultFS) ReuseForWrite(oldname, newname string) (File, error) {
   205  	if err := fs.Rename(oldname, newname); err != nil {
   206  		return nil, errors.WithStack(err)
   207  	}
   208  	f, err := os.OpenFile(newname, os.O_RDWR|os.O_CREATE|syscall.O_CLOEXEC, 0666)
   209  	return f, errors.WithStack(err)
   210  }
   211  
   212  func (fs defaultFS) OpenForWrite(name string) (File, error) {
   213  	f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND|syscall.O_CLOEXEC, 0666)
   214  	return f, errors.WithStack(err)
   215  }
   216  
   217  func (fs defaultFS) OpenWR(name string) (File, error) {
   218  	f, err := os.OpenFile(name, os.O_RDWR|syscall.O_CLOEXEC, 0666)
   219  	return f, errors.WithStack(err)
   220  }
   221  
   222  func (defaultFS) MkdirAll(dir string, perm os.FileMode) error {
   223  	return errors.WithStack(os.MkdirAll(dir, perm))
   224  }
   225  
   226  func (defaultFS) List(dir string) ([]string, error) {
   227  	f, err := os.Open(dir)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	defer f.Close()
   232  	dirnames, err := f.Readdirnames(-1)
   233  	return dirnames, errors.WithStack(err)
   234  }
   235  
   236  func (defaultFS) Stat(name string) (os.FileInfo, error) {
   237  	finfo, err := os.Stat(name)
   238  	return finfo, errors.WithStack(err)
   239  }
   240  
   241  func (defaultFS) PathBase(path string) string {
   242  	return filepath.Base(path)
   243  }
   244  
   245  func (defaultFS) PathJoin(elem ...string) string {
   246  	return filepath.Join(elem...)
   247  }
   248  
   249  func (defaultFS) PathDir(path string) string {
   250  	return filepath.Dir(path)
   251  }
   252  
   253  type sequentialReadsOption struct{}
   254  
   255  // SequentialReadsOption is an OpenOption that optimizes opened file handle for
   256  // sequential reads, by calling fadvise() with POSIX_FADV_SEQUENTIAL on Linux
   257  // systems to enable readahead.
   258  var SequentialReadsOption OpenOption = &sequentialReadsOption{}
   259  
   260  // Apply implements the OpenOption interface.
   261  func (sequentialReadsOption) Apply(f File) {
   262  	type fd interface {
   263  		Fd() uintptr
   264  	}
   265  	if fdFile, ok := f.(fd); ok {
   266  		_ = fadviseSequential(fdFile.Fd())
   267  	}
   268  }
   269  
   270  // Copy copies the contents of oldname to newname. If newname exists, it will
   271  // be overwritten.
   272  func Copy(fs FS, oldname, newname string) error {
   273  	src, err := fs.Open(oldname)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	defer src.Close()
   278  
   279  	dst, err := fs.Create(newname)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	defer dst.Close()
   284  
   285  	if _, err := io.Copy(dst, src); err != nil {
   286  		return err
   287  	}
   288  	return dst.Sync()
   289  }
   290  
   291  // LinkOrCopy creates newname as a hard link to the oldname file. If creating
   292  // the hard link fails, LinkOrCopy falls back to copying the file (which may
   293  // also fail if newname doesn't exist or oldname already exists).
   294  func LinkOrCopy(fs FS, oldname, newname string) error {
   295  	err := fs.Link(oldname, newname)
   296  	if err == nil {
   297  		return nil
   298  	}
   299  	// Permit a handful of errors which we know won't be fixed by copying the
   300  	// file. Note that we don't check for the specifics of the error code as it
   301  	// isn't easy to do so in a portable manner. On Unix we'd have to check for
   302  	// LinkError.Err == syscall.EXDEV. On Windows we'd have to check for
   303  	// ERROR_NOT_SAME_DEVICE, ERROR_INVALID_FUNCTION, and
   304  	// ERROR_INVALID_PARAMETER. Rather that such OS specific checks, we fall back
   305  	// to always trying to copy if hard-linking failed.
   306  	if oserror.IsExist(err) || oserror.IsNotExist(err) || oserror.IsPermission(err) {
   307  		return err
   308  	}
   309  	return Copy(fs, oldname, newname)
   310  }