]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Storages that use piece hashes would panic with pure v2 torrents
authorMatt Joiner <anacrolix@gmail.com>
Wed, 6 Mar 2024 11:49:48 +0000 (22:49 +1100)
committerMatt Joiner <anacrolix@gmail.com>
Wed, 6 Mar 2024 11:49:48 +0000 (22:49 +1100)
cmd/torrent-verify/main.go [deleted file]
metainfo/piece.go
piece.go
storage/interface.go
storage/issue95_test.go
storage/issue96_test.go
storage/piece-resource.go
storage/sqlite/direct.go
storage/wrappers.go

diff --git a/cmd/torrent-verify/main.go b/cmd/torrent-verify/main.go
deleted file mode 100644 (file)
index 0fbf024..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-package main
-
-import (
-       "bytes"
-       "crypto/sha1"
-       "fmt"
-       "io"
-       "log"
-       "os"
-       "path/filepath"
-
-       "github.com/anacrolix/tagflag"
-       "github.com/edsrzf/mmap-go"
-
-       "github.com/anacrolix/torrent/metainfo"
-       "github.com/anacrolix/torrent/mmap_span"
-       "github.com/anacrolix/torrent/storage"
-)
-
-func mmapFile(name string) (mm storage.FileMapping, err error) {
-       f, err := os.Open(name)
-       if err != nil {
-               return
-       }
-       defer func() {
-               if err != nil {
-                       f.Close()
-               }
-       }()
-       fi, err := f.Stat()
-       if err != nil {
-               return
-       }
-       if fi.Size() == 0 {
-               return
-       }
-       reg, err := mmap.MapRegion(f, -1, mmap.RDONLY, mmap.COPY, 0)
-       if err != nil {
-               return
-       }
-       return storage.WrapFileMapping(reg, f), nil
-}
-
-func verifyTorrent(info *metainfo.Info, root string) error {
-       span := new(mmap_span.MMapSpan)
-       for _, file := range info.UpvertedFiles() {
-               filename := filepath.Join(append([]string{root, info.Name}, file.Path...)...)
-               mm, err := mmapFile(filename)
-               if err != nil {
-                       return err
-               }
-               if int64(len(mm.Bytes())) != file.Length {
-                       return fmt.Errorf("file %q has wrong length", filename)
-               }
-               span.Append(mm)
-       }
-       span.InitIndex()
-       for i, numPieces := 0, info.NumPieces(); i < numPieces; i += 1 {
-               p := info.Piece(i)
-               hash := sha1.New()
-               _, err := io.Copy(hash, io.NewSectionReader(span, p.Offset(), p.Length()))
-               if err != nil {
-                       return err
-               }
-               good := bytes.Equal(hash.Sum(nil), p.Hash().Bytes())
-               if !good {
-                       return fmt.Errorf("hash mismatch at piece %d", i)
-               }
-               fmt.Printf("%d: %v: %v\n", i, p.Hash(), good)
-       }
-       return nil
-}
-
-func main() {
-       log.SetFlags(log.Flags() | log.Lshortfile)
-       flags := struct {
-               DataDir string
-               tagflag.StartPos
-               TorrentFile string
-       }{}
-       tagflag.Parse(&flags)
-       metaInfo, err := metainfo.LoadFromFile(flags.TorrentFile)
-       if err != nil {
-               log.Fatal(err)
-       }
-       info, err := metaInfo.UnmarshalInfo()
-       if err != nil {
-               log.Fatalf("error unmarshalling info: %s", err)
-       }
-       err = verifyTorrent(&info, flags.DataDir)
-       if err != nil {
-               log.Fatalf("torrent failed verification: %s", err)
-       }
-}
index 4972e529267e4f2bb3901dbb5c41ecb49fac6b93..c1c71221c3d5c79220b9d7d4838bc56c3c62d4e5 100644 (file)
@@ -1,5 +1,9 @@
 package metainfo
 
+import (
+       g "github.com/anacrolix/generics"
+)
+
 type Piece struct {
        Info *Info // Can we embed the fields here instead, or is it something to do with saving memory?
        i    pieceIndex
@@ -41,8 +45,12 @@ func (p Piece) Offset() int64 {
        return int64(p.i) * p.Info.PieceLength
 }
 
-func (p Piece) Hash() (ret Hash) {
-       copy(ret[:], p.Info.Pieces[p.i*HashSize:(p.i+1)*HashSize])
+func (p Piece) V1Hash() (ret g.Option[Hash]) {
+       if !p.Info.HasV1() {
+               return
+       }
+       copy(ret.Value[:], p.Info.Pieces[p.i*HashSize:(p.i+1)*HashSize])
+       ret.Ok = true
        return
 }
 
index 607e2f883f475f4b8db5abf16a8c314591d23693..0aa1e6339626b141e02403f1d5586512f55fd579 100644 (file)
--- a/piece.go
+++ b/piece.go
@@ -56,7 +56,13 @@ func (p *Piece) Info() metainfo.Piece {
 }
 
 func (p *Piece) Storage() storage.Piece {
-       return p.t.storage.Piece(p.Info())
+       var pieceHash g.Option[[]byte]
+       if p.hash != nil {
+               pieceHash.Set(p.hash.Bytes())
+       } else if p.hashV2.Ok {
+               pieceHash.Set(p.hashV2.Value.Bytes())
+       }
+       return p.t.storage.PieceWithHash(p.Info(), pieceHash)
 }
 
 func (p *Piece) Flush() {
index 9e8de06c8f55313370e938d386f234f45b22e3d3..41b4835ef46911a4809081ebb00b337028afa2ba 100644 (file)
@@ -3,6 +3,8 @@ package storage
 import (
        "io"
 
+       g "github.com/anacrolix/generics"
+
        "github.com/anacrolix/torrent/metainfo"
 )
 
@@ -20,9 +22,13 @@ type TorrentCapacity *func() (cap int64, capped bool)
 
 // Data storage bound to a torrent.
 type TorrentImpl struct {
+       // v2 infos might not have the piece hash available even if we have the info. The
+       // metainfo.Piece.Hash method was removed to enforce this.
        Piece func(p metainfo.Piece) PieceImpl
-       Close func() error
-       Flush func() error
+       // Preferred over PieceWithHash. Called with the piece hash if it's available.
+       PieceWithHash func(p metainfo.Piece, pieceHash g.Option[[]byte]) PieceImpl
+       Close         func() error
+       Flush         func() error
        // Storages that share the same space, will provide equal pointers. The function is called once
        // to determine the storage for torrents sharing the same function pointer, and mutated in
        // place.
index 92370797b6698248458d934aea6a6e498ceb04d6..61acf17e914c3c8edbafdb97d3a5c893e5249cb6 100644 (file)
@@ -12,11 +12,12 @@ import (
 
 // Two different torrents opened from the same storage. Closing one should not
 // break the piece completion on the other.
-func testIssue95(t *testing.T, c ClientImpl) {
+func testIssue95(t *testing.T, ci ClientImpl) {
        i1 := &metainfo.Info{
                Files:  []metainfo.FileInfo{{Path: []string{"a"}}},
                Pieces: make([]byte, 20),
        }
+       c := NewClient(ci)
        t1, err := c.OpenTorrent(i1, metainfo.HashBytes([]byte("a")))
        require.NoError(t, err)
        defer t1.Close()
index 726c11c803148e80bffa2df3166e605505bf3b0b..1be98aa5e738cd8976e7587d96759638175be916 100644 (file)
@@ -1,6 +1,7 @@
 package storage
 
 import (
+       g "github.com/anacrolix/generics"
        "testing"
 
        "github.com/stretchr/testify/require"
@@ -19,7 +20,7 @@ func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) ClientImplCl
        }
        ts, err := cs.OpenTorrent(info, metainfo.Hash{})
        require.NoError(t, err)
-       p := ts.Piece(info.Piece(0))
+       p := ts.PieceWithHash(info.Piece(0), g.None[[]byte]())
        require.NoError(t, p.MarkComplete())
        // require.False(t, p.GetIsComplete())
        n, err := p.ReadAt(make([]byte, 1), 0)
index 9e3e63e7fa6bd75c004ed630acb7a9ee18ca1fa6..94325027b047a27f52f25e9d5871b3a3e04d4aa1 100644 (file)
@@ -2,12 +2,14 @@ package storage
 
 import (
        "bytes"
+       "encoding/hex"
        "fmt"
        "io"
        "path"
        "sort"
        "strconv"
 
+       g "github.com/anacrolix/generics"
        "github.com/anacrolix/missinggo/v2/resource"
        "github.com/anacrolix/sync"
 
@@ -53,12 +55,13 @@ func (s piecePerResource) OpenTorrent(info *metainfo.Info, infoHash metainfo.Has
                s,
                make([]sync.RWMutex, info.NumPieces()),
        }
-       return TorrentImpl{Piece: t.Piece, Close: t.Close}, nil
+       return TorrentImpl{PieceWithHash: t.Piece, Close: t.Close}, nil
 }
 
-func (s piecePerResourceTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
+func (s piecePerResourceTorrentImpl) Piece(p metainfo.Piece, pieceHash g.Option[[]byte]) PieceImpl {
        return piecePerResourcePiece{
                mp:               p,
+               pieceHash:        pieceHash,
                piecePerResource: s.piecePerResource,
                mu:               &s.locks[p.Index()],
        }
@@ -74,6 +77,8 @@ type ConsecutiveChunkReader interface {
 
 type piecePerResourcePiece struct {
        mp metainfo.Piece
+       // The piece hash if we have it. It could be 20 or 32 bytes depending on the info version.
+       pieceHash g.Option[[]byte]
        piecePerResource
        // This protects operations that move complete/incomplete pieces around, which can trigger read
        // errors that may cause callers to do more drastic things.
@@ -118,7 +123,10 @@ func (s piecePerResourcePiece) mustIsComplete() bool {
        return completion.Complete
 }
 
-func (s piecePerResourcePiece) Completion() Completion {
+func (s piecePerResourcePiece) Completion() (_ Completion) {
+       if !s.pieceHash.Ok {
+               return
+       }
        s.mu.RLock()
        defer s.mu.RUnlock()
        fi, err := s.completed().Stat()
@@ -255,7 +263,7 @@ func (s piecePerResourcePiece) getChunks() (chunks chunks) {
 }
 
 func (s piecePerResourcePiece) completedInstancePath() string {
-       return path.Join("completed", s.mp.Hash().HexString())
+       return path.Join("completed", s.hashHex())
 }
 
 func (s piecePerResourcePiece) completed() resource.Instance {
@@ -267,7 +275,7 @@ func (s piecePerResourcePiece) completed() resource.Instance {
 }
 
 func (s piecePerResourcePiece) incompleteDirPath() string {
-       return path.Join("incompleted", s.mp.Hash().HexString())
+       return path.Join("incompleted", s.hashHex())
 }
 
 func (s piecePerResourcePiece) incompleteDir() resource.DirInstance {
@@ -277,3 +285,7 @@ func (s piecePerResourcePiece) incompleteDir() resource.DirInstance {
        }
        return i.(resource.DirInstance)
 }
+
+func (me piecePerResourcePiece) hashHex() string {
+       return hex.EncodeToString(me.pieceHash.Unwrap())
+}
index 9758ddb8d4cf3228aeba184285fafe3200e52fc5..e4e6981ca8e1bab541a0ef0130bd5cf07a9d428d 100644 (file)
@@ -4,10 +4,12 @@
 package sqliteStorage
 
 import (
+       "encoding/hex"
        "io"
        "sync"
        "time"
 
+       g "github.com/anacrolix/generics"
        "github.com/anacrolix/squirrel"
 
        "github.com/anacrolix/torrent/metainfo"
@@ -63,16 +65,16 @@ func (c *client) capacity() (cap int64, capped bool) {
 func (c *client) OpenTorrent(*metainfo.Info, metainfo.Hash) (storage.TorrentImpl, error) {
        t := torrent{c.cache}
        capFunc := c.capacity
-       return storage.TorrentImpl{Piece: t.Piece, Close: t.Close, Capacity: &capFunc}, nil
+       return storage.TorrentImpl{PieceWithHash: t.Piece, Close: t.Close, Capacity: &capFunc}, nil
 }
 
 type torrent struct {
        c *squirrel.Cache
 }
 
-func (t torrent) Piece(p metainfo.Piece) storage.PieceImpl {
+func (t torrent) Piece(p metainfo.Piece, pieceHash g.Option[[]byte]) storage.PieceImpl {
        ret := piece{
-               sb: t.c.OpenWithLength(p.Hash().HexString(), p.Length()),
+               sb: t.c.OpenWithLength(hex.EncodeToString(pieceHash.Unwrap()), p.Length()),
        }
        ret.ReaderAt = &ret.sb
        ret.WriterAt = &ret.sb
index a3907e1d2f4ed6a193c1a6cdedd85b9dbabf6e58..8dbfdd0aae97ed2cf7bf07787ba1b39f72800c41 100644 (file)
@@ -4,6 +4,7 @@ import (
        "io"
        "os"
 
+       g "github.com/anacrolix/generics"
        "github.com/anacrolix/missinggo/v2"
 
        "github.com/anacrolix/torrent/metainfo"
@@ -29,8 +30,19 @@ type Torrent struct {
        TorrentImpl
 }
 
+// Deprecated. Use PieceWithHash, as this doesn't work with pure v2 torrents.
 func (t Torrent) Piece(p metainfo.Piece) Piece {
-       return Piece{t.TorrentImpl.Piece(p), p}
+       return t.PieceWithHash(p, g.Some(p.V1Hash().Unwrap().Bytes()))
+}
+
+func (t Torrent) PieceWithHash(p metainfo.Piece, pieceHash g.Option[[]byte]) Piece {
+       var pieceImpl PieceImpl
+       if t.TorrentImpl.PieceWithHash != nil {
+               pieceImpl = t.TorrentImpl.PieceWithHash(p, pieceHash)
+       } else {
+               pieceImpl = t.TorrentImpl.Piece(p)
+       }
+       return Piece{pieceImpl, p}
 }
 
 type Piece struct {