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

Generated by: LCOV version 1.14