github.com/sunvim/utils@v0.1.0/fs/os_mmap.go (about)

     1  package fs
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  )
     7  
     8  const (
     9  	initialMmapSize = 1024 << 20 // 1 GiB
    10  )
    11  
    12  type osMMapFS struct {
    13  	osFS
    14  }
    15  
    16  // OSMMap is a file system backed by the os package and memory-mapped files.
    17  var OSMMap FileSystem = &osMMapFS{}
    18  
    19  func (fs *osMMapFS) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
    20  	if flag&os.O_APPEND != 0 {
    21  		// osMMapFS doesn't support opening files in append-only mode.
    22  		// The database doesn't currently use O_APPEND.
    23  		return nil, errAppendModeNotSupported
    24  	}
    25  
    26  	// open file and mmap
    27  	f, err := os.OpenFile(name, flag, perm)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	stat, err := f.Stat()
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	mf := &osMMapFile{
    38  		File: f,
    39  		size: stat.Size(),
    40  	}
    41  	if err := mf.mremap(); err != nil {
    42  		return nil, err
    43  	}
    44  	return mf, nil
    45  }
    46  
    47  type osMMapFile struct {
    48  	*os.File
    49  	data     []byte
    50  	offset   int64
    51  	size     int64
    52  	mmapSize int64
    53  }
    54  
    55  func (f *osMMapFile) WriteAt(p []byte, off int64) (int, error) {
    56  	n, err := f.File.WriteAt(p, off)
    57  	if err != nil {
    58  		return 0, err
    59  	}
    60  	writeOff := off + int64(n)
    61  	if writeOff > f.size {
    62  		f.size = writeOff
    63  	}
    64  	return n, f.mremap()
    65  }
    66  
    67  func (f *osMMapFile) Write(p []byte) (int, error) {
    68  	n, err := f.File.Write(p)
    69  	if err != nil {
    70  		return 0, err
    71  	}
    72  	f.offset += int64(n)
    73  	if f.offset > f.size {
    74  		f.size = f.offset
    75  	}
    76  	return n, f.mremap()
    77  }
    78  
    79  func (f *osMMapFile) Seek(offset int64, whence int) (int64, error) {
    80  	off, err := f.File.Seek(offset, whence)
    81  	f.offset = off
    82  	return off, err
    83  }
    84  
    85  func (f *osMMapFile) Read(p []byte) (int, error) {
    86  	n, err := f.File.Read(p)
    87  	f.offset += int64(n)
    88  	return n, err
    89  }
    90  
    91  func (f *osMMapFile) Slice(start int64, end int64) ([]byte, error) {
    92  	if end > f.size {
    93  		return nil, io.EOF
    94  	}
    95  	if f.data == nil {
    96  		return nil, os.ErrClosed
    97  	}
    98  	return f.data[start:end], nil
    99  }
   100  
   101  func (f *osMMapFile) munmap() error {
   102  	if f.data == nil {
   103  		return nil
   104  	}
   105  	if err := Munmap(f.data); err != nil {
   106  		return err
   107  	}
   108  	f.data = nil
   109  	f.mmapSize = 0
   110  	return nil
   111  }
   112  
   113  func (f *osMMapFile) mmap(mappingSize int64) error {
   114  	if f.data != nil {
   115  		if err := Munmap(f.data); err != nil {
   116  			return err
   117  		}
   118  	}
   119  
   120  	data, err := Mmap(f.File, mappingSize)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	_ = MadviceRandom(data)
   126  
   127  	f.data = data
   128  	return nil
   129  }
   130  
   131  func (f *osMMapFile) mremap() error {
   132  	mmapSize := f.mmapSize
   133  
   134  	if mmapSize >= f.size {
   135  		return nil
   136  	}
   137  
   138  	if mmapSize == 0 {
   139  		mmapSize = initialMmapSize
   140  		if mmapSize < f.size {
   141  			mmapSize = f.size
   142  		}
   143  	} else {
   144  		if err := f.munmap(); err != nil {
   145  			return err
   146  		}
   147  		mmapSize *= 2
   148  	}
   149  
   150  	if err := f.mmap(mmapSize); err != nil {
   151  		return err
   152  	}
   153  
   154  	// On Windows mmap may memory-map less than the requested size.
   155  	f.mmapSize = int64(len(f.data))
   156  
   157  	return nil
   158  }
   159  
   160  func (f *osMMapFile) Close() error {
   161  	if err := f.munmap(); err != nil {
   162  		return err
   163  	}
   164  	return f.File.Close()
   165  }