LCOV - code coverage report
Current view: top level - pebble/vfs - vfs.go (source / functions) Hit Total Coverage
Test: 2023-12-15 08:16Z ab4952c5 - meta test only.lcov Lines: 97 149 65.1 %
Date: 2023-12-15 08:16:54 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package vfs
       6             : 
       7             : import (
       8             :         "io"
       9             :         "os"
      10             :         "path/filepath"
      11             :         "syscall"
      12             : 
      13             :         "github.com/cockroachdb/errors"
      14             :         "github.com/cockroachdb/errors/oserror"
      15             : )
      16             : 
      17             : // File is a readable, writable sequence of bytes.
      18             : //
      19             : // Typically, it will be an *os.File, but test code may choose to substitute
      20             : // memory-backed implementations.
      21             : //
      22             : // Write-oriented operations (Write, Sync) must be called sequentially: At most
      23             : // 1 call to Write or Sync may be executed at any given time.
      24             : type File interface {
      25             :         io.Closer
      26             :         io.Reader
      27             :         io.ReaderAt
      28             :         // Unlike the specification for io.Writer.Write(), the vfs.File.Write()
      29             :         // method *is* allowed to modify the slice passed in, whether temporarily
      30             :         // or permanently. Callers of Write() need to take this into account.
      31             :         io.Writer
      32             :         // WriteAt() is only supported for files that were opened with FS.OpenReadWrite.
      33             :         io.WriterAt
      34             : 
      35             :         // Preallocate optionally preallocates storage for `length` at `offset`
      36             :         // within the file. Implementations may choose to do nothing.
      37             :         Preallocate(offset, length int64) error
      38             :         Stat() (os.FileInfo, error)
      39             :         Sync() error
      40             : 
      41             :         // SyncTo requests that a prefix of the file's data be synced to stable
      42             :         // storage. The caller passes provides a `length`, indicating how many bytes
      43             :         // to sync from the beginning of the file. SyncTo is a no-op for
      44             :         // directories, and therefore always returns false.
      45             :         //
      46             :         // SyncTo returns a fullSync return value, indicating one of two possible
      47             :         // outcomes.
      48             :         //
      49             :         // If fullSync is false, the first `length` bytes of the file was queued to
      50             :         // be synced to stable storage. The syncing of the file prefix may happen
      51             :         // asynchronously. No persistence guarantee is provided.
      52             :         //
      53             :         // If fullSync is true, the entirety of the file's contents were
      54             :         // synchronously synced to stable storage, and a persistence guarantee is
      55             :         // provided. In this outcome, any modified metadata for the file is not
      56             :         // guaranteed to be synced unless that metadata is needed in order to allow
      57             :         // a subsequent data retrieval to be correctly handled.
      58             :         SyncTo(length int64) (fullSync bool, err error)
      59             : 
      60             :         // SyncData requires that all written data be persisted. File metadata is
      61             :         // not required to be synced. Unsophisticated implementations may call Sync.
      62             :         SyncData() error
      63             : 
      64             :         // Prefetch signals the OS (on supported platforms) to fetch the next length
      65             :         // bytes in file (as returned by os.File.Fd()) after offset into cache. Any
      66             :         // subsequent reads in that range will not issue disk IO.
      67             :         Prefetch(offset int64, length int64) error
      68             : 
      69             :         // Fd returns the raw file descriptor when a File is backed by an *os.File.
      70             :         // It can be used for specific functionality like Prefetch.
      71             :         // Returns InvalidFd if not supported.
      72             :         Fd() uintptr
      73             : }
      74             : 
      75             : // InvalidFd is a special value returned by File.Fd() when the file is not
      76             : // backed by an OS descriptor.
      77             : // Note: the special value is consistent with what os.File implementation
      78             : // returns on a nil receiver.
      79             : const InvalidFd uintptr = ^(uintptr(0))
      80             : 
      81             : // OpenOption provide an interface to do work on file handles in the Open()
      82             : // call.
      83             : type OpenOption interface {
      84             :         // Apply is called on the file handle after it's opened.
      85             :         Apply(File)
      86             : }
      87             : 
      88             : // FS is a namespace for files.
      89             : //
      90             : // The names are filepath names: they may be / separated or \ separated,
      91             : // depending on the underlying operating system.
      92             : type FS interface {
      93             :         // Create creates the named file for reading and writing. If a file
      94             :         // already exists at the provided name, it's removed first ensuring the
      95             :         // resulting file descriptor points to a new inode.
      96             :         Create(name string) (File, error)
      97             : 
      98             :         // Link creates newname as a hard link to the oldname file.
      99             :         Link(oldname, newname string) error
     100             : 
     101             :         // Open opens the named file for reading. openOptions provides
     102             :         Open(name string, opts ...OpenOption) (File, error)
     103             : 
     104             :         // OpenReadWrite opens the named file for reading and writing. If the file
     105             :         // does not exist, it is created.
     106             :         OpenReadWrite(name string, opts ...OpenOption) (File, error)
     107             : 
     108             :         // OpenDir opens the named directory for syncing.
     109             :         OpenDir(name string) (File, error)
     110             : 
     111             :         // Remove removes the named file or directory.
     112             :         Remove(name string) error
     113             : 
     114             :         // Remove removes the named file or directory and any children it
     115             :         // contains. It removes everything it can but returns the first error it
     116             :         // encounters.
     117             :         RemoveAll(name string) error
     118             : 
     119             :         // Rename renames a file. It overwrites the file at newname if one exists,
     120             :         // the same as os.Rename.
     121             :         Rename(oldname, newname string) error
     122             : 
     123             :         // ReuseForWrite attempts to reuse the file with oldname by renaming it to newname and opening
     124             :         // it for writing without truncation. It is acceptable for the implementation to choose not
     125             :         // to reuse oldname, and simply create the file with newname -- in this case the implementation
     126             :         // should delete oldname. If the caller calls this function with an oldname that does not exist,
     127             :         // the implementation may return an error.
     128             :         ReuseForWrite(oldname, newname string) (File, error)
     129             : 
     130             :         // MkdirAll creates a directory and all necessary parents. The permission
     131             :         // bits perm have the same semantics as in os.MkdirAll. If the directory
     132             :         // already exists, MkdirAll does nothing and returns nil.
     133             :         MkdirAll(dir string, perm os.FileMode) error
     134             : 
     135             :         // Lock locks the given file, creating the file if necessary, and
     136             :         // truncating the file if it already exists. The lock is an exclusive lock
     137             :         // (a write lock), but locked files should neither be read from nor written
     138             :         // to. Such files should have zero size and only exist to co-ordinate
     139             :         // ownership across processes.
     140             :         //
     141             :         // A nil Closer is returned if an error occurred. Otherwise, close that
     142             :         // Closer to release the lock.
     143             :         //
     144             :         // On Linux and OSX, a lock has the same semantics as fcntl(2)'s advisory
     145             :         // locks. In particular, closing any other file descriptor for the same
     146             :         // file will release the lock prematurely.
     147             :         //
     148             :         // Attempting to lock a file that is already locked by the current process
     149             :         // returns an error and leaves the existing lock untouched.
     150             :         //
     151             :         // Lock is not yet implemented on other operating systems, and calling it
     152             :         // will return an error.
     153             :         Lock(name string) (io.Closer, error)
     154             : 
     155             :         // List returns a listing of the given directory. The names returned are
     156             :         // relative to dir.
     157             :         List(dir string) ([]string, error)
     158             : 
     159             :         // Stat returns an os.FileInfo describing the named file.
     160             :         Stat(name string) (os.FileInfo, error)
     161             : 
     162             :         // PathBase returns the last element of path. Trailing path separators are
     163             :         // removed before extracting the last element. If the path is empty, PathBase
     164             :         // returns ".".  If the path consists entirely of separators, PathBase returns a
     165             :         // single separator.
     166             :         PathBase(path string) string
     167             : 
     168             :         // PathJoin joins any number of path elements into a single path, adding a
     169             :         // separator if necessary.
     170             :         PathJoin(elem ...string) string
     171             : 
     172             :         // PathDir returns all but the last element of path, typically the path's directory.
     173             :         PathDir(path string) string
     174             : 
     175             :         // GetDiskUsage returns disk space statistics for the filesystem where
     176             :         // path is any file or directory within that filesystem.
     177             :         GetDiskUsage(path string) (DiskUsage, error)
     178             : }
     179             : 
     180             : // DiskUsage summarizes disk space usage on a filesystem.
     181             : type DiskUsage struct {
     182             :         // Total disk space available to the current process in bytes.
     183             :         AvailBytes uint64
     184             :         // Total disk space in bytes.
     185             :         TotalBytes uint64
     186             :         // Used disk space in bytes.
     187             :         UsedBytes uint64
     188             : }
     189             : 
     190             : // Default is a FS implementation backed by the underlying operating system's
     191             : // file system.
     192             : var Default FS = defaultFS{}
     193             : 
     194             : type defaultFS struct{}
     195             : 
     196             : // wrapOSFile takes a standard library OS file and returns a vfs.File. f may be
     197             : // nil, in which case wrapOSFile must not panic. In such cases, it's okay if the
     198             : // returned vfs.File may panic if used.
     199           1 : func wrapOSFile(f *os.File) File {
     200           1 :         // See the implementations in default_{linux,unix,windows}.go.
     201           1 :         return wrapOSFileImpl(f)
     202           1 : }
     203             : 
     204           1 : func (defaultFS) Create(name string) (File, error) {
     205           1 :         const openFlags = os.O_RDWR | os.O_CREATE | os.O_EXCL | syscall.O_CLOEXEC
     206           1 : 
     207           1 :         osFile, err := os.OpenFile(name, openFlags, 0666)
     208           1 :         // If the file already exists, remove it and try again.
     209           1 :         //
     210           1 :         // NB: We choose to remove the file instead of truncating it, despite the
     211           1 :         // fact that we can't do so atomically, because it's more resistant to
     212           1 :         // misuse when using hard links.
     213           1 : 
     214           1 :         // We must loop in case another goroutine/thread/process is also
     215           1 :         // attempting to create the a file at the same path.
     216           1 :         for oserror.IsExist(err) {
     217           1 :                 if removeErr := os.Remove(name); removeErr != nil && !oserror.IsNotExist(removeErr) {
     218           0 :                         return wrapOSFile(osFile), errors.WithStack(removeErr)
     219           0 :                 }
     220           1 :                 osFile, err = os.OpenFile(name, openFlags, 0666)
     221             :         }
     222           1 :         return wrapOSFile(osFile), errors.WithStack(err)
     223             : }
     224             : 
     225           1 : func (defaultFS) Link(oldname, newname string) error {
     226           1 :         return errors.WithStack(os.Link(oldname, newname))
     227           1 : }
     228             : 
     229           1 : func (defaultFS) Open(name string, opts ...OpenOption) (File, error) {
     230           1 :         osFile, err := os.OpenFile(name, os.O_RDONLY|syscall.O_CLOEXEC, 0)
     231           1 :         if err != nil {
     232           1 :                 return nil, errors.WithStack(err)
     233           1 :         }
     234           1 :         file := wrapOSFile(osFile)
     235           1 :         for _, opt := range opts {
     236           0 :                 opt.Apply(file)
     237           0 :         }
     238           1 :         return file, nil
     239             : }
     240             : 
     241           0 : func (defaultFS) OpenReadWrite(name string, opts ...OpenOption) (File, error) {
     242           0 :         osFile, err := os.OpenFile(name, os.O_RDWR|syscall.O_CLOEXEC|os.O_CREATE, 0666)
     243           0 :         if err != nil {
     244           0 :                 return nil, errors.WithStack(err)
     245           0 :         }
     246           0 :         file := wrapOSFile(osFile)
     247           0 :         for _, opt := range opts {
     248           0 :                 opt.Apply(file)
     249           0 :         }
     250           0 :         return file, nil
     251             : }
     252             : 
     253           1 : func (defaultFS) Remove(name string) error {
     254           1 :         return errors.WithStack(os.Remove(name))
     255           1 : }
     256             : 
     257           0 : func (defaultFS) RemoveAll(name string) error {
     258           0 :         return errors.WithStack(os.RemoveAll(name))
     259           0 : }
     260             : 
     261           1 : func (defaultFS) Rename(oldname, newname string) error {
     262           1 :         return errors.WithStack(os.Rename(oldname, newname))
     263           1 : }
     264             : 
     265           0 : func (fs defaultFS) ReuseForWrite(oldname, newname string) (File, error) {
     266           0 :         if err := fs.Rename(oldname, newname); err != nil {
     267           0 :                 return nil, errors.WithStack(err)
     268           0 :         }
     269           0 :         f, err := os.OpenFile(newname, os.O_RDWR|os.O_CREATE|syscall.O_CLOEXEC, 0666)
     270           0 :         return wrapOSFile(f), errors.WithStack(err)
     271             : }
     272             : 
     273           1 : func (defaultFS) MkdirAll(dir string, perm os.FileMode) error {
     274           1 :         return errors.WithStack(os.MkdirAll(dir, perm))
     275           1 : }
     276             : 
     277           1 : func (defaultFS) List(dir string) ([]string, error) {
     278           1 :         f, err := os.Open(dir)
     279           1 :         if err != nil {
     280           0 :                 return nil, err
     281           0 :         }
     282           1 :         defer f.Close()
     283           1 :         dirnames, err := f.Readdirnames(-1)
     284           1 :         return dirnames, errors.WithStack(err)
     285             : }
     286             : 
     287           1 : func (defaultFS) Stat(name string) (os.FileInfo, error) {
     288           1 :         finfo, err := os.Stat(name)
     289           1 :         return finfo, errors.WithStack(err)
     290           1 : }
     291             : 
     292           1 : func (defaultFS) PathBase(path string) string {
     293           1 :         return filepath.Base(path)
     294           1 : }
     295             : 
     296           1 : func (defaultFS) PathJoin(elem ...string) string {
     297           1 :         return filepath.Join(elem...)
     298           1 : }
     299             : 
     300           1 : func (defaultFS) PathDir(path string) string {
     301           1 :         return filepath.Dir(path)
     302           1 : }
     303             : 
     304             : type randomReadsOption struct{}
     305             : 
     306             : // RandomReadsOption is an OpenOption that optimizes opened file handle for
     307             : // random reads, by calling  fadvise() with POSIX_FADV_RANDOM on Linux systems
     308             : // to disable readahead.
     309             : var RandomReadsOption OpenOption = &randomReadsOption{}
     310             : 
     311             : // Apply implements the OpenOption interface.
     312           1 : func (randomReadsOption) Apply(f File) {
     313           1 :         if fd := f.Fd(); fd != InvalidFd {
     314           1 :                 _ = fadviseRandom(fd)
     315           1 :         }
     316             : }
     317             : 
     318             : type sequentialReadsOption struct{}
     319             : 
     320             : // SequentialReadsOption is an OpenOption that optimizes opened file handle for
     321             : // sequential reads, by calling fadvise() with POSIX_FADV_SEQUENTIAL on Linux
     322             : // systems to enable readahead.
     323             : var SequentialReadsOption OpenOption = &sequentialReadsOption{}
     324             : 
     325             : // Apply implements the OpenOption interface.
     326           1 : func (sequentialReadsOption) Apply(f File) {
     327           1 :         if fd := f.Fd(); fd != InvalidFd {
     328           1 :                 _ = fadviseSequential(fd)
     329           1 :         }
     330             : }
     331             : 
     332             : // Copy copies the contents of oldname to newname. If newname exists, it will
     333             : // be overwritten.
     334           1 : func Copy(fs FS, oldname, newname string) error {
     335           1 :         return CopyAcrossFS(fs, oldname, fs, newname)
     336           1 : }
     337             : 
     338             : // CopyAcrossFS copies the contents of oldname on srcFS to newname dstFS. If
     339             : // newname exists, it will be overwritten.
     340           1 : func CopyAcrossFS(srcFS FS, oldname string, dstFS FS, newname string) error {
     341           1 :         src, err := srcFS.Open(oldname, SequentialReadsOption)
     342           1 :         if err != nil {
     343           0 :                 return err
     344           0 :         }
     345           1 :         defer src.Close()
     346           1 : 
     347           1 :         dst, err := dstFS.Create(newname)
     348           1 :         if err != nil {
     349           0 :                 return err
     350           0 :         }
     351           1 :         defer dst.Close()
     352           1 : 
     353           1 :         if _, err := io.Copy(dst, src); err != nil {
     354           0 :                 return err
     355           0 :         }
     356           1 :         return dst.Sync()
     357             : }
     358             : 
     359             : // LimitedCopy copies up to maxBytes from oldname to newname. If newname
     360             : // exists, it will be overwritten.
     361           0 : func LimitedCopy(fs FS, oldname, newname string, maxBytes int64) error {
     362           0 :         src, err := fs.Open(oldname, SequentialReadsOption)
     363           0 :         if err != nil {
     364           0 :                 return err
     365           0 :         }
     366           0 :         defer src.Close()
     367           0 : 
     368           0 :         dst, err := fs.Create(newname)
     369           0 :         if err != nil {
     370           0 :                 return err
     371           0 :         }
     372           0 :         defer dst.Close()
     373           0 : 
     374           0 :         if _, err := io.Copy(dst, &io.LimitedReader{R: src, N: maxBytes}); err != nil {
     375           0 :                 return err
     376           0 :         }
     377           0 :         return dst.Sync()
     378             : }
     379             : 
     380             : // LinkOrCopy creates newname as a hard link to the oldname file. If creating
     381             : // the hard link fails, LinkOrCopy falls back to copying the file (which may
     382             : // also fail if oldname doesn't exist or newname already exists).
     383           1 : func LinkOrCopy(fs FS, oldname, newname string) error {
     384           1 :         err := fs.Link(oldname, newname)
     385           1 :         if err == nil {
     386           1 :                 return nil
     387           1 :         }
     388             :         // Permit a handful of errors which we know won't be fixed by copying the
     389             :         // file. Note that we don't check for the specifics of the error code as it
     390             :         // isn't easy to do so in a portable manner. On Unix we'd have to check for
     391             :         // LinkError.Err == syscall.EXDEV. On Windows we'd have to check for
     392             :         // ERROR_NOT_SAME_DEVICE, ERROR_INVALID_FUNCTION, and
     393             :         // ERROR_INVALID_PARAMETER. Rather that such OS specific checks, we fall back
     394             :         // to always trying to copy if hard-linking failed.
     395           0 :         if oserror.IsExist(err) || oserror.IsNotExist(err) || oserror.IsPermission(err) {
     396           0 :                 return err
     397           0 :         }
     398           0 :         return Copy(fs, oldname, newname)
     399             : }
     400             : 
     401             : // Root returns the base FS implementation, unwrapping all nested FSs that
     402             : // expose an Unwrap method.
     403           1 : func Root(fs FS) FS {
     404           1 :         type unwrapper interface {
     405           1 :                 Unwrap() FS
     406           1 :         }
     407           1 : 
     408           1 :         for {
     409           1 :                 u, ok := fs.(unwrapper)
     410           1 :                 if !ok {
     411           1 :                         break
     412             :                 }
     413           1 :                 fs = u.Unwrap()
     414             :         }
     415           1 :         return fs
     416             : }
     417             : 
     418             : // ErrUnsupported may be returned a FS when it does not support an operation.
     419             : var ErrUnsupported = errors.New("pebble: not supported")

Generated by: LCOV version 1.14