+//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.InfoHash]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.InfoHash]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 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()
+ }
+ fileErr := m.f.Close()
+ if err == nil {
+ err = fileErr
}
return
}
+
+func (m mmapWithFile) Bytes() []byte {
+ if m.mmap == nil {
+ return nil
+ }
+ return m.mmap
+}