github.com/anacrolix/torrent@v1.61.0/storage/mmap.go (about)

     1  //go:build !wasm
     2  // +build !wasm
     3  
     4  package storage
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/anacrolix/missinggo/v2"
    15  	"github.com/edsrzf/mmap-go"
    16  
    17  	"github.com/anacrolix/torrent/metainfo"
    18  	mmapSpan "github.com/anacrolix/torrent/mmap-span"
    19  )
    20  
    21  type mmapClientImpl struct {
    22  	baseDir string
    23  	pc      PieceCompletion
    24  }
    25  
    26  // TODO: Support all the same native filepath configuration that NewFileOpts provides.
    27  func NewMMap(baseDir string) ClientImplCloser {
    28  	return NewMMapWithCompletion(baseDir, pieceCompletionForDir(baseDir))
    29  }
    30  
    31  func NewMMapWithCompletion(baseDir string, completion PieceCompletion) *mmapClientImpl {
    32  	return &mmapClientImpl{
    33  		baseDir: baseDir,
    34  		pc:      completion,
    35  	}
    36  }
    37  
    38  func (s *mmapClientImpl) OpenTorrent(
    39  	_ context.Context,
    40  	info *metainfo.Info,
    41  	infoHash metainfo.Hash,
    42  ) (_ TorrentImpl, err error) {
    43  	span, err := mMapTorrent(info, s.baseDir)
    44  	t := &mmapTorrentStorage{
    45  		infoHash: infoHash,
    46  		span:     span,
    47  		pc:       s.pc,
    48  	}
    49  	return TorrentImpl{Piece: t.Piece, Close: t.Close}, err
    50  }
    51  
    52  func (s *mmapClientImpl) Close() error {
    53  	return s.pc.Close()
    54  }
    55  
    56  type mmapTorrentStorage struct {
    57  	infoHash metainfo.Hash
    58  	span     *mmapSpan.MMapSpan
    59  	pc       PieceCompletionGetSetter
    60  }
    61  
    62  func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
    63  	return mmapStoragePiece{
    64  		t:        ts,
    65  		p:        p,
    66  		ReaderAt: io.NewSectionReader(ts.span, p.Offset(), p.Length()),
    67  		WriterAt: missinggo.NewSectionWriter(ts.span, p.Offset(), p.Length()),
    68  	}
    69  }
    70  
    71  func (ts *mmapTorrentStorage) Close() error {
    72  	return ts.span.Close()
    73  }
    74  
    75  type mmapStoragePiece struct {
    76  	t *mmapTorrentStorage
    77  	p metainfo.Piece
    78  	io.ReaderAt
    79  	io.WriterAt
    80  }
    81  
    82  func (me mmapStoragePiece) Flush() error {
    83  	// TODO: Flush just the regions of the files we care about. At least this is no worse than it
    84  	// was previously.
    85  	return me.t.span.Flush()
    86  }
    87  
    88  func (me mmapStoragePiece) pieceKey() metainfo.PieceKey {
    89  	return metainfo.PieceKey{me.t.infoHash, me.p.Index()}
    90  }
    91  
    92  func (sp mmapStoragePiece) Completion() Completion {
    93  	c, err := sp.t.pc.Get(sp.pieceKey())
    94  	if err != nil {
    95  		panic(err)
    96  	}
    97  	return c
    98  }
    99  
   100  func (sp mmapStoragePiece) MarkComplete() error {
   101  	err := sp.t.pc.Set(sp.pieceKey(), true)
   102  	if err == nil {
   103  		err = sp.Flush()
   104  	}
   105  	return err
   106  }
   107  
   108  func (sp mmapStoragePiece) MarkNotComplete() error {
   109  	return sp.t.pc.Set(sp.pieceKey(), false)
   110  }
   111  
   112  func mMapTorrent(md *metainfo.Info, location string) (mms *mmapSpan.MMapSpan, err error) {
   113  	var mMaps []FileMapping
   114  	defer func() {
   115  		if err != nil {
   116  			for _, mm := range mMaps {
   117  				err = errors.Join(err, mm.Unmap())
   118  			}
   119  		}
   120  	}()
   121  	for _, miFile := range md.UpvertedFiles() {
   122  		var safeName string
   123  		safeName, err = ToSafeFilePath(append([]string{md.BestName()}, miFile.BestPath()...)...)
   124  		if err != nil {
   125  			return
   126  		}
   127  		fileName := filepath.Join(location, safeName)
   128  		var mm FileMapping
   129  		mm, err = mmapFile(fileName, miFile.Length)
   130  		if err != nil {
   131  			err = fmt.Errorf("file %q: %w", miFile.DisplayPath(md), err)
   132  			return
   133  		}
   134  		mMaps = append(mMaps, mm)
   135  	}
   136  	return mmapSpan.New(mMaps, md.FileSegmentsIndex()), nil
   137  }
   138  
   139  func mmapFile(name string, size int64) (_ FileMapping, err error) {
   140  	dir := filepath.Dir(name)
   141  	err = os.MkdirAll(dir, 0o750)
   142  	if err != nil {
   143  		err = fmt.Errorf("making directory %q: %s", dir, err)
   144  		return
   145  	}
   146  	var file *os.File
   147  	file, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR, filePerm)
   148  	if err != nil {
   149  		return
   150  	}
   151  	defer func() {
   152  		if err != nil {
   153  			file.Close()
   154  		}
   155  	}()
   156  	var fi os.FileInfo
   157  	fi, err = file.Stat()
   158  	if err != nil {
   159  		return
   160  	}
   161  	if fi.Size() < size {
   162  		// I think this is necessary on HFS+. Maybe Linux will SIGBUS too if
   163  		// you overmap a file but I'm not sure.
   164  		err = file.Truncate(size)
   165  		if err != nil {
   166  			return
   167  		}
   168  	}
   169  	return func() (ret mmapWithFile, err error) {
   170  		ret.f = file
   171  		if size == 0 {
   172  			// Can't mmap() regions with length 0.
   173  			return
   174  		}
   175  		intLen := int(size)
   176  		if int64(intLen) != size {
   177  			err = errors.New("size too large for system")
   178  			return
   179  		}
   180  		ret.mmap, err = mmap.MapRegion(file, intLen, mmap.RDWR, 0, 0)
   181  		if err != nil {
   182  			err = fmt.Errorf("error mapping region: %s", err)
   183  			return
   184  		}
   185  		if int64(len(ret.mmap)) != size {
   186  			panic(len(ret.mmap))
   187  		}
   188  		return
   189  	}()
   190  }
   191  
   192  // Combines a mmapped region and file into a storage Mmap abstraction, which handles closing the
   193  // mmap file handle.
   194  func WrapFileMapping(region mmap.MMap, file *os.File) FileMapping {
   195  	return mmapWithFile{
   196  		f:    file,
   197  		mmap: region,
   198  	}
   199  }
   200  
   201  type FileMapping = mmapSpan.Mmap
   202  
   203  // Handles closing the mmap's file handle (needed for Windows). Could be implemented differently by
   204  // OS.
   205  type mmapWithFile struct {
   206  	f    *os.File
   207  	mmap mmap.MMap
   208  }
   209  
   210  func (m mmapWithFile) Flush() error {
   211  	return m.mmap.Flush()
   212  }
   213  
   214  func (m mmapWithFile) Unmap() (err error) {
   215  	if m.mmap != nil {
   216  		err = m.mmap.Unmap()
   217  	}
   218  	fileErr := m.f.Close()
   219  	if err == nil {
   220  		err = fileErr
   221  	}
   222  	return
   223  }
   224  
   225  func (m mmapWithFile) Bytes() []byte {
   226  	if m.mmap == nil {
   227  		return nil
   228  	}
   229  	return m.mmap
   230  }