]> Sergey Matveev's repositories - btrtrc.git/blobdiff - storage/mmap.go
Fixes for storage tests on Windows
[btrtrc.git] / storage / mmap.go
index d5327a9b7d90f9284903a665c8a802657170f534..1b5d05a24390bff05afeaabfda38d432a6097fff 100644 (file)
+//go:build !wasm
+// +build !wasm
+
 package storage
 
 import (
+       "errors"
        "fmt"
        "io"
        "os"
        "path/filepath"
 
-       "github.com/anacrolix/missinggo"
+       "github.com/anacrolix/missinggo/v2"
        "github.com/edsrzf/mmap-go"
 
        "github.com/anacrolix/torrent/metainfo"
        "github.com/anacrolix/torrent/mmap_span"
 )
 
-type mmapStorage struct {
+type mmapClientImpl struct {
        baseDir string
+       pc      PieceCompletion
+}
+
+// TODO: Support all the same native filepath configuration that NewFileOpts provides.
+func NewMMap(baseDir string) ClientImplCloser {
+       return NewMMapWithCompletion(baseDir, pieceCompletionForDir(baseDir))
 }
 
-func NewMMap(baseDir string) I {
-       return &mmapStorage{
+func NewMMapWithCompletion(baseDir string, completion PieceCompletion) *mmapClientImpl {
+       return &mmapClientImpl{
                baseDir: baseDir,
+               pc:      completion,
        }
 }
 
-func (me *mmapStorage) OpenTorrent(info *metainfo.InfoEx) (t Torrent, err error) {
-       span, err := MMapTorrent(&info.Info, me.baseDir)
-       t = &mmapTorrentStorage{
-               span: span,
+func (s *mmapClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ TorrentImpl, err error) {
+       span, err := mMapTorrent(info, s.baseDir)
+       t := &mmapTorrentStorage{
+               infoHash: infoHash,
+               span:     span,
+               pc:       s.pc,
        }
-       return
+       return TorrentImpl{Piece: t.Piece, Close: t.Close, Flush: t.Flush}, err
+}
+
+func (s *mmapClientImpl) Close() error {
+       return s.pc.Close()
 }
 
 type mmapTorrentStorage struct {
-       span      mmap_span.MMapSpan
-       completed map[metainfo.Hash]bool
+       infoHash metainfo.Hash
+       span     *mmap_span.MMapSpan
+       pc       PieceCompletionGetSetter
 }
 
-func (me *mmapTorrentStorage) Piece(p metainfo.Piece) Piece {
+func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
        return mmapStoragePiece{
-               storage:  me,
+               pc:       ts.pc,
                p:        p,
-               ReaderAt: io.NewSectionReader(me.span, p.Offset(), p.Length()),
-               WriterAt: missinggo.NewSectionWriter(me.span, p.Offset(), p.Length()),
+               ih:       ts.infoHash,
+               ReaderAt: io.NewSectionReader(ts.span, p.Offset(), p.Length()),
+               WriterAt: missinggo.NewSectionWriter(ts.span, p.Offset(), p.Length()),
        }
 }
 
-func (me *mmapTorrentStorage) Close() error {
-       me.span.Close()
+func (ts *mmapTorrentStorage) Close() error {
+       errs := ts.span.Close()
+       if len(errs) > 0 {
+               return errs[0]
+       }
+       return nil
+}
+
+func (ts *mmapTorrentStorage) Flush() error {
+       errs := ts.span.Flush()
+       if len(errs) > 0 {
+               return errs[0]
+       }
        return nil
 }
 
 type mmapStoragePiece struct {
-       storage *mmapTorrentStorage
-       p       metainfo.Piece
+       pc PieceCompletionGetSetter
+       p  metainfo.Piece
+       ih metainfo.Hash
        io.ReaderAt
        io.WriterAt
 }
 
-func (me mmapStoragePiece) GetIsComplete() bool {
-       return me.storage.completed[me.p.Hash()]
+func (me mmapStoragePiece) pieceKey() metainfo.PieceKey {
+       return metainfo.PieceKey{me.ih, me.p.Index()}
 }
 
-func (me mmapStoragePiece) MarkComplete() error {
-       if me.storage.completed == nil {
-               me.storage.completed = make(map[metainfo.Hash]bool)
+func (sp mmapStoragePiece) Completion() Completion {
+       c, err := sp.pc.Get(sp.pieceKey())
+       if err != nil {
+               panic(err)
        }
-       me.storage.completed[me.p.Hash()] = true
+       return c
+}
+
+func (sp mmapStoragePiece) MarkComplete() error {
+       sp.pc.Set(sp.pieceKey(), true)
        return nil
 }
 
-func MMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
+func (sp mmapStoragePiece) MarkNotComplete() error {
+       sp.pc.Set(sp.pieceKey(), false)
+       return nil
+}
+
+func mMapTorrent(md *metainfo.Info, location string) (mms *mmap_span.MMapSpan, err error) {
+       mms = &mmap_span.MMapSpan{}
        defer func() {
                if err != nil {
                        mms.Close()
                }
        }()
        for _, miFile := range md.UpvertedFiles() {
-               fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...)
-               err = os.MkdirAll(filepath.Dir(fileName), 0777)
+               var safeName string
+               safeName, err = ToSafeFilePath(append([]string{md.Name}, miFile.Path...)...)
                if err != nil {
-                       err = fmt.Errorf("error creating data directory %q: %s", filepath.Dir(fileName), err)
                        return
                }
-               var file *os.File
-               file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
+               fileName := filepath.Join(location, safeName)
+               var mm FileMapping
+               mm, err = mmapFile(fileName, miFile.Length)
                if err != nil {
+                       err = fmt.Errorf("file %q: %s", miFile.DisplayPath(md), err)
                        return
                }
-               func() {
-                       defer file.Close()
-                       var fi os.FileInfo
-                       fi, err = file.Stat()
-                       if err != nil {
-                               return
-                       }
-                       if fi.Size() < miFile.Length {
-                               err = file.Truncate(miFile.Length)
-                               if err != nil {
-                                       return
-                               }
-                       }
-                       if miFile.Length == 0 {
-                               // Can't mmap() regions with length 0.
-                               return
-                       }
-                       var mMap mmap.MMap
-                       mMap, err = mmap.MapRegion(file,
-                               int(miFile.Length), // Probably not great on <64 bit systems.
-                               mmap.RDWR, 0, 0)
-                       if err != nil {
-                               err = fmt.Errorf("error mapping file %q, length %d: %s", file.Name(), miFile.Length, err)
-                               return
-                       }
-                       if int64(len(mMap)) != miFile.Length {
-                               panic("mmap has wrong length")
-                       }
-                       mms.Append(mMap)
-               }()
+               mms.Append(mm)
+       }
+       mms.InitIndex()
+       return
+}
+
+func mmapFile(name string, size int64) (_ FileMapping, err error) {
+       dir := filepath.Dir(name)
+       err = os.MkdirAll(dir, 0o750)
+       if err != nil {
+               err = fmt.Errorf("making directory %q: %s", dir, err)
+               return
+       }
+       var file *os.File
+       file, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0o666)
+       if err != nil {
+               return
+       }
+       defer func() {
+               if err != nil {
+                       file.Close()
+               }
+       }()
+       var fi os.FileInfo
+       fi, err = file.Stat()
+       if err != nil {
+               return
+       }
+       if fi.Size() < size {
+               // I think this is necessary on HFS+. Maybe Linux will SIGBUS too if
+               // you overmap a file but I'm not sure.
+               err = file.Truncate(size)
                if err != nil {
                        return
                }
        }
-       return
+       return func() (ret mmapWithFile, err error) {
+               ret.f = file
+               if size == 0 {
+                       // Can't mmap() regions with length 0.
+                       return
+               }
+               intLen := int(size)
+               if int64(intLen) != size {
+                       err = errors.New("size too large for system")
+                       return
+               }
+               ret.mmap, err = mmap.MapRegion(file, intLen, mmap.RDWR, 0, 0)
+               if err != nil {
+                       err = fmt.Errorf("error mapping region: %s", err)
+                       return
+               }
+               if int64(len(ret.mmap)) != size {
+                       panic(len(ret.mmap))
+               }
+               return
+       }()
+}
+
+// Combines a mmapped region and file into a storage Mmap abstraction, which handles closing the
+// mmap file handle.
+func WrapFileMapping(region mmap.MMap, file *os.File) FileMapping {
+       return mmapWithFile{
+               f:    file,
+               mmap: region,
+       }
+}
+
+type FileMapping = mmap_span.Mmap
+
+// Handles closing the mmap's file handle (needed for Windows). Could be implemented differently by
+// OS.
+type mmapWithFile struct {
+       f    *os.File
+       mmap mmap.MMap
+}
+
+func (m mmapWithFile) Flush() error {
+       return m.mmap.Flush()
+}
+
+func (m mmapWithFile) Unmap() (err error) {
+       if m.mmap != nil {
+               err = m.mmap.Unmap()
+       }
+       return errors.Join(err, m.f.Close())
+}
+
+func (m mmapWithFile) Bytes() []byte {
+       if m.mmap == nil {
+               return nil
+       }
+       return m.mmap
 }