]> Sergey Matveev's repositories - btrtrc.git/blob - storage/mmap.go
Remove old-style build tags
[btrtrc.git] / storage / mmap.go
1 //go:build !wasm
2
3 package storage
4
5 import (
6         "errors"
7         "fmt"
8         "io"
9         "os"
10         "path/filepath"
11
12         "github.com/anacrolix/missinggo/v2"
13         "github.com/edsrzf/mmap-go"
14
15         "github.com/anacrolix/torrent/metainfo"
16         "github.com/anacrolix/torrent/mmap_span"
17 )
18
19 type mmapClientImpl struct {
20         baseDir string
21         pc      PieceCompletion
22 }
23
24 // TODO: Support all the same native filepath configuration that NewFileOpts provides.
25 func NewMMap(baseDir string) ClientImplCloser {
26         return NewMMapWithCompletion(baseDir, pieceCompletionForDir(baseDir))
27 }
28
29 func NewMMapWithCompletion(baseDir string, completion PieceCompletion) *mmapClientImpl {
30         return &mmapClientImpl{
31                 baseDir: baseDir,
32                 pc:      completion,
33         }
34 }
35
36 func (s *mmapClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (_ TorrentImpl, err error) {
37         span, err := mMapTorrent(info, s.baseDir)
38         t := &mmapTorrentStorage{
39                 infoHash: infoHash,
40                 span:     span,
41                 pc:       s.pc,
42         }
43         return TorrentImpl{Piece: t.Piece, Close: t.Close}, err
44 }
45
46 func (s *mmapClientImpl) Close() error {
47         return s.pc.Close()
48 }
49
50 type mmapTorrentStorage struct {
51         infoHash metainfo.Hash
52         span     *mmap_span.MMapSpan
53         pc       PieceCompletionGetSetter
54 }
55
56 func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
57         return mmapStoragePiece{
58                 pc:       ts.pc,
59                 p:        p,
60                 ih:       ts.infoHash,
61                 ReaderAt: io.NewSectionReader(ts.span, p.Offset(), p.Length()),
62                 WriterAt: missinggo.NewSectionWriter(ts.span, p.Offset(), p.Length()),
63         }
64 }
65
66 func (ts *mmapTorrentStorage) Close() error {
67         errs := ts.span.Close()
68         if len(errs) > 0 {
69                 return errs[0]
70         }
71         return nil
72 }
73
74 type mmapStoragePiece struct {
75         pc PieceCompletionGetSetter
76         p  metainfo.Piece
77         ih metainfo.Hash
78         io.ReaderAt
79         io.WriterAt
80 }
81
82 func (me mmapStoragePiece) pieceKey() metainfo.PieceKey {
83         return metainfo.PieceKey{me.ih, me.p.Index()}
84 }
85
86 func (sp mmapStoragePiece) Completion() Completion {
87         c, err := sp.pc.Get(sp.pieceKey())
88         if err != nil {
89                 panic(err)
90         }
91         return c
92 }
93
94 func (sp mmapStoragePiece) MarkComplete() error {
95         sp.pc.Set(sp.pieceKey(), true)
96         return nil
97 }
98
99 func (sp mmapStoragePiece) MarkNotComplete() error {
100         sp.pc.Set(sp.pieceKey(), false)
101         return nil
102 }
103
104 func mMapTorrent(md *metainfo.Info, location string) (mms *mmap_span.MMapSpan, err error) {
105         mms = &mmap_span.MMapSpan{}
106         defer func() {
107                 if err != nil {
108                         mms.Close()
109                 }
110         }()
111         for _, miFile := range md.UpvertedFiles() {
112                 var safeName string
113                 safeName, err = ToSafeFilePath(append([]string{md.Name}, miFile.Path...)...)
114                 if err != nil {
115                         return
116                 }
117                 fileName := filepath.Join(location, safeName)
118                 var mm mmap.MMap
119                 mm, err = mmapFile(fileName, miFile.Length)
120                 if err != nil {
121                         err = fmt.Errorf("file %q: %s", miFile.DisplayPath(md), err)
122                         return
123                 }
124                 if mm != nil {
125                         mms.Append(mm)
126                 }
127         }
128         mms.InitIndex()
129         return
130 }
131
132 func mmapFile(name string, size int64) (ret mmap.MMap, err error) {
133         dir := filepath.Dir(name)
134         err = os.MkdirAll(dir, 0777)
135         if err != nil {
136                 err = fmt.Errorf("making directory %q: %s", dir, err)
137                 return
138         }
139         var file *os.File
140         file, err = os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0666)
141         if err != nil {
142                 return
143         }
144         defer file.Close()
145         var fi os.FileInfo
146         fi, err = file.Stat()
147         if err != nil {
148                 return
149         }
150         if fi.Size() < size {
151                 // I think this is necessary on HFS+. Maybe Linux will SIGBUS too if
152                 // you overmap a file but I'm not sure.
153                 err = file.Truncate(size)
154                 if err != nil {
155                         return
156                 }
157         }
158         if size == 0 {
159                 // Can't mmap() regions with length 0.
160                 return
161         }
162         intLen := int(size)
163         if int64(intLen) != size {
164                 err = errors.New("size too large for system")
165                 return
166         }
167         ret, err = mmap.MapRegion(file, intLen, mmap.RDWR, 0, 0)
168         if err != nil {
169                 err = fmt.Errorf("error mapping region: %s", err)
170                 return
171         }
172         if int64(len(ret)) != size {
173                 panic(len(ret))
174         }
175         return
176 }