github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/util/memmap/memmap.go (about)

     1  // Copyright 2016 Stanislav Liberman
     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 memmap
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"log"
    21  	"os"
    22  	"sync"
    23  	"syscall"
    24  	"unsafe"
    25  
    26  	mapper "github.com/edsrzf/mmap-go"
    27  	"github.com/lirm/aeron-go/aeron/logging"
    28  )
    29  
    30  // File is the wrapper around memory mapped file
    31  type File struct {
    32  	mmap unsafe.Pointer
    33  	size int
    34  }
    35  
    36  var logger = logging.MustGetLogger("memmap")
    37  
    38  // We keep this map so that we can get back the original handle from the memory address.
    39  // map[unsafe.Pointer]mapper.MMap
    40  var memories = sync.Map{}
    41  
    42  // GetFileSize is a helper function to retrieve file size
    43  func GetFileSize(filename string) int64 {
    44  	file, err := os.Open(filename)
    45  	if err != nil {
    46  		logger.Error(err)
    47  		return -1
    48  	}
    49  	defer file.Close()
    50  
    51  	fi, err := file.Stat()
    52  	if err != nil {
    53  		logger.Fatal(err)
    54  		return -1
    55  	}
    56  
    57  	return fi.Size()
    58  }
    59  
    60  // MapExisting is the factory method used to create memory maps for existing file. This function will fail if
    61  // the file does not exist.
    62  func MapExisting(filename string, offset int64, length int) (*File, error) {
    63  	logger.Debugf("Will try to map existing %s, %d, %d", filename, offset, length)
    64  
    65  	f, err := os.OpenFile(filename, syscall.O_RDWR, 0644)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	defer f.Close()
    70  	fi, err := f.Stat()
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	size := fi.Size()
    76  	if size == 0 {
    77  		return nil, errors.New("zero size for existing file")
    78  	}
    79  	if size < 0 {
    80  		return nil, fmt.Errorf("mmap: stat %q returned %d", filename, size)
    81  	}
    82  	if size != int64(size) {
    83  		return nil, fmt.Errorf("mmap: file %q is too large", filename)
    84  	}
    85  
    86  	var mapSize int
    87  	if length != 0 {
    88  		mapSize = length
    89  	} else {
    90  		mapSize = int(size)
    91  	}
    92  
    93  	logger.Debugf("Mapping existing file: fd: %d, size: %d, offset: %d", f.Fd(), size, offset)
    94  	mmap, err := doMap(f, offset, mapSize)
    95  	logger.Debugf("Mapped existing file @%v for %d", mmap.mmap, mmap.size)
    96  
    97  	return mmap, err
    98  }
    99  
   100  // NewFile is a factory method to create a new memory mapped file with the specified capacity
   101  func NewFile(filename string, offset int64, length int) (*File, error) {
   102  	logger.Debugf("Will try to map new %s, %d, %d", filename, offset, length)
   103  
   104  	f, err := os.Create(filename)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	defer f.Close()
   109  
   110  	_, err = f.Seek(int64(length-1), 0)
   111  	if err != nil {
   112  		log.Fatal(err)
   113  	}
   114  	_, err = f.Write([]byte("\000"))
   115  	if err != nil {
   116  		log.Fatal(err)
   117  	}
   118  
   119  	mmap, err := doMap(f, offset, length)
   120  	logger.Debugf("Mapped a new file @%v for %d", mmap.mmap, mmap.size)
   121  
   122  	return mmap, err
   123  }
   124  
   125  // GetMemoryPtr return the pointer to the mapped region of the file
   126  func (mmap *File) GetMemoryPtr() unsafe.Pointer {
   127  	return mmap.mmap
   128  }
   129  
   130  // GetMemorySize return the size of the mapped region of the file
   131  func (mmap *File) GetMemorySize() int {
   132  	return mmap.size
   133  }
   134  
   135  // Close attempts to unmap the mapped memory region
   136  func (mmap *File) Close() (err error) {
   137  	mm, _ := memories.Load(mmap.mmap)
   138  	tomap := mm.(mapper.MMap)
   139  	return tomap.Unmap()
   140  }
   141  
   142  func doMap(f *os.File, offset int64, length int) (*File, error) {
   143  
   144  	mm, err := mapper.MapRegion(f, length, mapper.RDWR, 0, offset)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	mmap := new(File)
   150  	mmap.mmap = unsafe.Pointer(&mm[0])
   151  	mmap.size = length
   152  
   153  	memories.Store(mmap.mmap, mm)
   154  
   155  	return mmap, err
   156  }