mmap.go 4.8 KB

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