.circleci/config.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ client_test.go | 64 +++++++++++++++++++++++++++--------------------------- fs/file_handle.go | 20 +++++++++++++++----- fs/test.sh | 18 +++++++++--------- internal/cmd/issue-464/main.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ internal/tmproot/dir.go | 56 ----------------------------------------------------- issue211_test.go | 2 +- main_test.go | 2 -- peerconn_test.go | 2 +- pexconn_test.go | 2 +- reader_test.go | 2 +- storage/sqlite/sqlite-storage.go | 11 ++++++++++- test/init_test.go | 5 ----- test/issue377_test.go | 4 ++-- test/transfer_test.go | 10 +++++----- test/unix_test.go | 2 +- testing.go | 8 +++----- torrent.go | 6 +++--- torrent_test.go | 10 +++++----- webseed-peer.go | 16 ++++++++++++---- webseed/client.go | 17 ++++++++++++++--- diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c9d2f439285894c81c53724801a79758c93f5d7..f25b011d42b79f8f0ba122a5773191fbb80e7c49 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,11 +47,11 @@ - restore_cache: keys: - go-cache- - run: go get -d ./... - - run: go test -v -race ./... -count 2 -bench . + - run: go test -v -race ./... -count 2 - run: go test -bench . ./... - run: set +e; CGO_ENABLED=0 go test -v ./...; true - run: GOARCH=386 go test ./... -count 2 -bench . || true - - run: go install ./cmd/torrentfs + - run: go install github.com/anacrolix/godo@latest - save_cache: key: go-pkg-{{ checksum "go.mod" }} paths: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000000000000000000000000000000000..c41d86b8c8ee5a434e7d7df61e587885cf62ea8d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: "CodeQL" + +on: + push: + branches: [master, ] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 10 * * 4' + +jobs: + analyse: + name: Analyse + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/client_test.go b/client_test.go index 6e7a8dad171c020d99348923496758d8859e0bc4..5abc83e4df4121d76d8d9ed6758d60eaffa44bda 100644 --- a/client_test.go +++ b/client_test.go @@ -29,7 +29,7 @@ "github.com/anacrolix/torrent/storage" ) func TestClientDefault(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) cl.Close() } @@ -41,7 +41,7 @@ cl.Close() } func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) pc, err := storage.NewBoltPieceCompletion(cfg.DataDir) require.NoError(t, err) ci := storage.NewFileWithCompletion(cfg.DataDir, pc) @@ -57,7 +57,7 @@ cl.Close() } func TestAddDropTorrent(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() dir, mi := testutil.GreetingTestTorrent() @@ -93,12 +93,12 @@ func TestTorrentInitialState(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(dir) cl := &Client{ - config: TestingConfig(), + config: TestingConfig(t), } cl.initLogger() tor := cl.newTorrent( mi.HashInfoBytes(), - storage.NewFileWithCompletion(TestingTempDir.NewSub(), storage.NewMapPieceCompletion()), + storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()), ) tor.setChunkSize(2) tor.cl.lock() @@ -140,7 +140,7 @@ } } func TestAddDropManyTorrents(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() for i := range iter.N(1000) { @@ -163,7 +163,7 @@ ) } func TestMergingTrackersByAddingSpecs(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() spec := TorrentSpec{} @@ -181,7 +181,7 @@ } // We read from a piece which is marked completed, but is missing data. func TestCompletedPieceWrongSize(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DefaultStorage = badStorage{} cl, err := NewClient(cfg) require.NoError(t, err) @@ -208,7 +208,7 @@ quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil) } func BenchmarkAddLargeTorrent(b *testing.B) { - cfg := TestingConfig() + cfg := TestingConfig(b) cfg.DisableTCP = true cfg.DisableUTP = true cl, err := NewClient(cfg) @@ -227,7 +227,7 @@ func TestResponsive(t *testing.T) { seederDataDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(seederDataDir) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Seed = true cfg.DataDir = seederDataDir seeder, err := NewClient(cfg) @@ -238,7 +238,7 @@ seederTorrent.VerifyData() leecherDataDir, err := ioutil.TempDir("", "") require.Nil(t, err) defer os.RemoveAll(leecherDataDir) - cfg = TestingConfig() + cfg = TestingConfig(t) cfg.DataDir = leecherDataDir leecher, err := NewClient(cfg) require.Nil(t, err) @@ -270,7 +270,7 @@ func TestTorrentDroppedDuringResponsiveRead(t *testing.T) { seederDataDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(seederDataDir) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Seed = true cfg.DataDir = seederDataDir seeder, err := NewClient(cfg) @@ -281,7 +281,7 @@ seederTorrent.VerifyData() leecherDataDir, err := ioutil.TempDir("", "") require.Nil(t, err) defer os.RemoveAll(leecherDataDir) - cfg = TestingConfig() + cfg = TestingConfig(t) cfg.DataDir = leecherDataDir leecher, err := NewClient(cfg) require.Nil(t, err) @@ -313,7 +313,7 @@ func TestDhtInheritBlocklist(t *testing.T) { ipl := iplist.New(nil) require.NotNil(t, ipl) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.IPBlocklist = ipl cfg.NoDHT = false cl, err := NewClient(cfg) @@ -331,7 +331,7 @@ // Check that stuff is merged in subsequent AddTorrentSpec for the same // infohash. func TestAddTorrentSpecMerging(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() dir, mi := testutil.GreetingTestTorrent() @@ -351,7 +351,7 @@ func TestTorrentDroppedBeforeGotInfo(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() os.RemoveAll(dir) - cl, _ := NewClient(TestingConfig()) + cl, _ := NewClient(TestingConfig(t)) defer cl.Close() tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{ InfoHash: mi.HashInfoBytes(), @@ -395,7 +395,7 @@ if alreadyCompleted { require.NoError(t, greetingData.Piece(p).MarkComplete()) } } - cfg := TestingConfig() + cfg := TestingConfig(t) // TODO: Disable network option? cfg.DisableTCP = true cfg.DisableUTP = true @@ -424,7 +424,7 @@ testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage) } func TestAddMetainfoWithNodes(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.ListenHost = func(string) string { return "" } cfg.NoDHT = false cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } } @@ -461,7 +461,7 @@ func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) { greetingTempDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Seed = true cfg.DataDir = greetingTempDir seeder, err := NewClient(cfg) @@ -530,7 +530,7 @@ } // Ensure that it's an error for a peer to send an invalid have message. func TestPeerInvalidHave(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DropMutuallyCompletePeers = false cl, err := NewClient(cfg) require.NoError(t, err) @@ -561,9 +561,9 @@ func TestPieceCompletedInStorageButNotClient(t *testing.T) { greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DataDir = greetingTempDir - seeder, err := NewClient(TestingConfig()) + seeder, err := NewClient(TestingConfig(t)) require.NoError(t, err) seeder.AddTorrentSpec(&TorrentSpec{ InfoBytes: greetingMetainfo.InfoBytes, @@ -573,7 +573,7 @@ // Check that when the listen port is 0, all the protocols listened on have // the same port, and it isn't zero. func TestClientDynamicListenPortAllProtocols(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() port := cl.LocalPort() @@ -585,7 +585,7 @@ }) } func TestClientDynamicListenTCPOnly(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DisableUTP = true cfg.DisableTCP = false cl, err := NewClient(cfg) @@ -595,7 +595,7 @@ assert.NotEqual(t, 0, cl.LocalPort()) } func TestClientDynamicListenUTPOnly(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DisableTCP = true cfg.DisableUTP = false cl, err := NewClient(cfg) @@ -616,7 +616,7 @@ func TestSetMaxEstablishedConn(t *testing.T) { var tts []*Torrent ih := testutil.GreetingMetaInfo().HashInfoBytes() - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DisableAcceptRateLimiting = true cfg.DropDuplicatePeerIds = true for i := range iter.N(3) { @@ -697,7 +697,7 @@ // Test that the leecher can download a torrent in its entirety from the seeder. Note that the // seeder config is done first. func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Seed = true cfg.DataDir = filepath.Join(cfg.DataDir, "server") os.Mkdir(cfg.DataDir, 0755) @@ -713,7 +713,7 @@ makeMagnet(t, server, cfg.DataDir, "test2") for i := 0; i < 100; i++ { makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2)) } - cfg = TestingConfig() + cfg = TestingConfig(t) cfg.DataDir = filepath.Join(cfg.DataDir, "client") leecher(cfg) client, err := NewClient(cfg) @@ -764,14 +764,14 @@ s, _ := NewUtpSocket("udp", ":50007", nil) if s != nil { defer s.Close() } - cfg := TestingConfig().SetListenAddr(":50007") + cfg := TestingConfig(t).SetListenAddr(":50007") cl, err := NewClient(cfg) require.Error(t, err) require.Nil(t, cl) } func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) { - cc := TestingConfig() + cc := TestingConfig(t) cc.DisableUTP = true cc.NoDHT = false cl, err := NewClient(cc) @@ -783,7 +783,7 @@ func TestIssue335(t *testing.T) { dir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(dir) - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Seed = false cfg.Debug = true cfg.DataDir = dir @@ -806,7 +806,7 @@ require.True(t, cl.WaitAll()) } func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.DisableTCP = true cfg.DisableUTP = true cfg.NoDHT = false diff --git a/fs/file_handle.go b/fs/file_handle.go index 41517296b2a0680ff4e59b2c5d49b3607fd8fbc7..64aeebef9a2dc5604a46f78b0aae3c3c2c3195fb 100644 --- a/fs/file_handle.go +++ b/fs/file_handle.go @@ -26,7 +26,8 @@ torrentfsReadRequests.Add(1) if req.Dir { panic("read on directory") } - pos, err := me.r.Seek(req.Offset, io.SeekStart) + r := me.r + pos, err := r.Seek(req.Offset, io.SeekStart) if err != nil { panic(err) } @@ -44,10 +45,19 @@ me.fn.FS.blockedReads++ me.fn.FS.event.Broadcast() me.fn.FS.mu.Unlock() var n int - r := missinggo.ContextedReader{me.r, ctx} - n, readErr = r.Read(resp.Data) - if readErr == io.EOF { - readErr = nil + r := missinggo.ContextedReader{r, ctx} + //log.Printf("reading %v bytes at %v", len(resp.Data), req.Offset) + if true { + // A user reported on that on freebsd 12.2, the system requires that reads are + // completely filled. Their system only asks for 64KiB at a time. I've seen systems that + // can demand up to 16MiB at a time, so this gets tricky. For now, I'll restore the old + // behaviour from before 2a7352a, which nobody reported problems with. + n, readErr = io.ReadFull(r, resp.Data) + } else { + n, readErr = r.Read(resp.Data) + if readErr == io.EOF { + readErr = nil + } } resp.Data = resp.Data[:n] }() diff --git a/fs/test.sh b/fs/test.sh index 116bbff28885cd3c1bd11c466aa323e1df7b07b0..08d303fc755461d9d3a6b3cb9fa0babf6c6ab49a 100755 --- a/fs/test.sh +++ b/fs/test.sh @@ -1,17 +1,17 @@ -set -ex -repopath=$(cd $(dirname $0)/..; pwd) -d=$(mktemp -d) -pushd "$d" -mkdir mnt torrents -GOPPROF=http torrentfs -mountDir=mnt -metainfoDir=torrents & -trap 'set +e; sudo umount -f mnt; pushd; rm -rv "$d"' EXIT +set -eux +repopath="$(cd "$(dirname "$0")/.."; pwd)" +mkdir -p mnt torrents +GOPPROF=http godo -v "$repopath/cmd/torrentfs" -mountDir=mnt -metainfoDir=torrents &> log & +trap 'set +e; sudo umount -f mnt' EXIT +debian_file=debian-10.8.0-amd64-netinst.iso pushd torrents -cp "$repopath"/testdata/debian-9.1.0-amd64-netinst.iso.torrent . +cp "$repopath/testdata/$debian_file.torrent" . echo 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d&dn=sintel.mp4' > sintel.magnet popd -file=debian-9.1.0-amd64-netinst.iso +file="$debian_file" # file=sintel.mp4 while [ ! -e "mnt/$file" ]; do sleep 1; done pv "mnt/$file" | md5sum +# expect e221f43f4fdd409250908fc4305727d4 sudo umount mnt wait || echo "wait returned" $? diff --git a/internal/cmd/issue-464/main.go b/internal/cmd/issue-464/main.go new file mode 100644 index 0000000000000000000000000000000000000000..fbac1b4372931592f770b2255d46960aa42ce66d --- /dev/null +++ b/internal/cmd/issue-464/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "io" + "log" + + "github.com/anacrolix/torrent" +) + +const testMagnet = "magnet:?xt=urn:btih:a88fda5954e89178c372716a6a78b8180ed4dad3&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F" + +func main() { + err := mainErr() + if err != nil { + log.Fatalf("error in main: %v", err) + } +} + +func mainErr() error { + cfg := torrent.NewDefaultClientConfig() + // We could disable non-webseed peer types here, to force any errors. + client, _ := torrent.NewClient(cfg) + + // Add directly from metainfo, because we want to force webseeding to serve data, and webseeding + // won't get us the metainfo. + t, err := client.AddTorrentFromFile("testdata/The WIRED CD - Rip. Sample. Mash. Share.torrent") + if err != nil { + return err + } + <-t.GotInfo() + + fmt.Println("GOT INFO") + + f := t.Files()[0] + + r := f.NewReader() + + r.Seek(5, io.SeekStart) + buf := make([]byte, 5) + n, err := r.Read(buf) + + fmt.Println("END", n, buf, err) + + t.DownloadAll() + client.WaitAll() + return nil +} diff --git a/internal/tmproot/dir.go b/internal/tmproot/dir.go deleted file mode 100644 index 1e078667fd83ff9cfb62e2c8338772cdc3515fe4..0000000000000000000000000000000000000000 --- a/internal/tmproot/dir.go +++ /dev/null @@ -1,56 +0,0 @@ -package tmproot - -import ( - "io/ioutil" - "os" - "sync" -) - -type Dir struct { - mu sync.Mutex - path string - inited bool -} - -func (me *Dir) init(prefix string) bool { - if me.inited { - return false - } - var err error - me.path, err = ioutil.TempDir("", prefix) - if err != nil { - panic(err) - } - me.inited = true - return true -} - -func (me *Dir) Init(prefix string) { - me.mu.Lock() - defer me.mu.Unlock() - if me.inited { - panic("already inited") - } - me.init(prefix) -} - -func (me *Dir) lazyDefaultInit() { - me.mu.Lock() - defer me.mu.Unlock() - me.init("") - -} - -func (me *Dir) NewSub() string { - me.lazyDefaultInit() - ret, err := ioutil.TempDir(me.path, "") - if err != nil { - panic(err) - } - return ret -} - -func (me *Dir) RemoveAll() error { - me.lazyDefaultInit() - return os.RemoveAll(me.path) -} diff --git a/issue211_test.go b/issue211_test.go index dedbaa5647a80a1337f94e10a7ef3aa38a2852b1..f33057e413972ee966c47fdb5e38cfe5346e6afc 100644 --- a/issue211_test.go +++ b/issue211_test.go @@ -14,7 +14,7 @@ "github.com/anacrolix/torrent/storage" ) func TestDropTorrentWithMmapStorageWhileHashing(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) // Ensure the data is present when the torrent is added, and not obtained // over the network as the test runs. cfg.DownloadRateLimiter = rate.NewLimiter(0, 0) diff --git a/main_test.go b/main_test.go index 301502c84a3c5f629760553eb13d2ff3d3b0b61c..51af26a041374468d11efece53c88b61999942e4 100644 --- a/main_test.go +++ b/main_test.go @@ -13,9 +13,7 @@ log.SetFlags(log.LstdFlags | log.Lshortfile) } func TestMain(m *testing.M) { - TestingTempDir.Init("torrent.test") code := m.Run() - TestingTempDir.RemoveAll() // select {} os.Exit(code) } diff --git a/peerconn_test.go b/peerconn_test.go index 654f7689fcd1d9fc105e14ff34d6e02e53be5066..7057e77dc9c3a1900c3a0356ec3939304fd5ded1 100644 --- a/peerconn_test.go +++ b/peerconn_test.go @@ -21,7 +21,7 @@ // Ensure that no race exists between sending a bitfield, and a subsequent // Have that would potentially alter it. func TestSendBitfieldThenHave(t *testing.T) { cl := Client{ - config: TestingConfig(), + config: TestingConfig(t), } cl.initLogger() c := cl.newConnection(nil, false, nil, "io.Pipe", "") diff --git a/pexconn_test.go b/pexconn_test.go index 609e088eee0dd21d1d877614497ef8e32112dc7b..0b78b70242f503605edb095db5112dcd751c3974 100644 --- a/pexconn_test.go +++ b/pexconn_test.go @@ -13,7 +13,7 @@ ) func TestPexConnState(t *testing.T) { cl := Client{ - config: TestingConfig(), + config: TestingConfig(t), } cl.initLogger() torrent := cl.newTorrent(metainfo.Hash{}, nil) diff --git a/reader_test.go b/reader_test.go index 86021dd60ae8c6bfff8af2a93c04ec7d49014483..c1017a0cb8af6488d8a037312f31b3be84125fb7 100644 --- a/reader_test.go +++ b/reader_test.go @@ -11,7 +11,7 @@ "github.com/anacrolix/torrent/internal/testutil" ) func TestReaderReadContext(t *testing.T) { - cl, err := NewClient(TestingConfig()) + cl, err := NewClient(TestingConfig(t)) require.NoError(t, err) defer cl.Close() tt, err := cl.AddTorrent(testutil.GreetingMetaInfo()) diff --git a/storage/sqlite/sqlite-storage.go b/storage/sqlite/sqlite-storage.go index 97f7181a8500cd6f6be23d44dd90f7e69937a973..4b5abb244067fcc950c62c9e215a144021928f90 100644 --- a/storage/sqlite/sqlite-storage.go +++ b/storage/sqlite/sqlite-storage.go @@ -187,6 +187,9 @@ }, nil } type NewPoolOpts struct { + // See https://www.sqlite.org/c3ref/open.html. NB: "If the filename is an empty string, then a + // private, temporary on-disk database will be created. This private database will be + // automatically deleted as soon as the database connection is closed." Path string Memory bool NumConns int @@ -364,12 +367,15 @@ var _ storage.ConsecutiveChunkReader = (*provider)(nil) func (p *provider) ReadConsecutiveChunks(prefix string) (io.ReadCloser, error) { + p.closeMu.RLock() runner, err := p.getReadWithConnRunner() if err != nil { + p.closeMu.RUnlock() return nil, err } r, w := io.Pipe() go func() { + defer p.closeMu.RUnlock() err = runner(func(_ context.Context, conn conn) error { var written int64 err = sqlitex.Exec(conn, ` @@ -503,6 +509,8 @@ } func (p *provider) withConn(with withConn, write bool, skip int) error { p.closeMu.RLock() + // I think we need to check this here because it may not be valid to send to the writes channel + // if we're already closed. So don't try to move this check into getReadWithConnRunner. if p.closed { p.closeMu.RUnlock() return errors.New("closed") @@ -527,7 +535,8 @@ } } // Obtains a DB conn and returns a withConn for executing with it. If no error is returned from this -// function, the runner *must* be used or the conn is leaked. +// function, the runner *must* be used or the conn is leaked. You should check the provider isn't +// closed before using this. func (p *provider) getReadWithConnRunner() (with func(withConn) error, err error) { conn := p.pool.Get(context.TODO()) if conn == nil { diff --git a/test/init_test.go b/test/init_test.go index 9e63ef83cba7ba92f1df9fea88138b9823bc4ff3..38335ef8c6391f9144d98300d04ea0a912c41e12 100644 --- a/test/init_test.go +++ b/test/init_test.go @@ -2,9 +2,4 @@ package test import ( _ "github.com/anacrolix/envpprof" - "github.com/anacrolix/torrent" ) - -func init() { - torrent.TestingTempDir.Init("torrent-test.test") -} diff --git a/test/issue377_test.go b/test/issue377_test.go index 4ede3576293f3d878b303e22180f1cac2dd906cb..bd8c4357d5a5e5e9366266fec633184791e8207f 100644 --- a/test/issue377_test.go +++ b/test/issue377_test.go @@ -36,7 +36,7 @@ func testReceiveChunkStorageFailure(t *testing.T, seederFast bool) { seederDataDir, metainfo := testutil.GreetingTestTorrent() defer os.RemoveAll(seederDataDir) - seederClientConfig := torrent.TestingConfig() + seederClientConfig := torrent.TestingConfig(t) seederClientConfig.Debug = true justOneNetwork(seederClientConfig) seederClientStorage := storage.NewMMap(seederDataDir) @@ -49,7 +49,7 @@ seederClient, err := torrent.NewClient(seederClientConfig) require.NoError(t, err) defer seederClient.Close() defer testutil.ExportStatusWriter(seederClient, "s", t)() - leecherClientConfig := torrent.TestingConfig() + leecherClientConfig := torrent.TestingConfig(t) leecherClientConfig.Debug = true justOneNetwork(leecherClientConfig) leecherClient, err := torrent.NewClient(leecherClientConfig) diff --git a/test/transfer_test.go b/test/transfer_test.go index 47efef0bb3a90fefbbaff96324a9c48a8c09e8eb..f232b186ee7ce495b25af720366c432625f76945 100644 --- a/test/transfer_test.go +++ b/test/transfer_test.go @@ -62,7 +62,7 @@ greetingTempDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) // Create seeder and a Torrent. - cfg := torrent.TestingConfig() + cfg := torrent.TestingConfig(t) cfg.Seed = true // Some test instances don't like this being on, even when there's no cache involved. cfg.DropMutuallyCompletePeers = false @@ -96,7 +96,7 @@ // Create leecher and a Torrent. leecherDataDir, err := ioutil.TempDir("", "") require.NoError(t, err) defer os.RemoveAll(leecherDataDir) - cfg = torrent.TestingConfig() + cfg = torrent.TestingConfig(t) // See the seeder client config comment. cfg.DropMutuallyCompletePeers = false if ps.LeecherStorage == nil { @@ -388,7 +388,7 @@ func TestSeedAfterDownloading(t *testing.T) { greetingTempDir, mi := testutil.GreetingTestTorrent() defer os.RemoveAll(greetingTempDir) - cfg := torrent.TestingConfig() + cfg := torrent.TestingConfig(t) cfg.Seed = true cfg.DataDir = greetingTempDir seeder, err := torrent.NewClient(cfg) @@ -400,7 +400,7 @@ require.NoError(t, err) assert.True(t, ok) seederTorrent.VerifyData() - cfg = torrent.TestingConfig() + cfg = torrent.TestingConfig(t) cfg.Seed = true cfg.DataDir, err = ioutil.TempDir("", "") require.NoError(t, err) @@ -410,7 +410,7 @@ require.NoError(t, err) defer leecher.Close() defer testutil.ExportStatusWriter(leecher, "l", t)() - cfg = torrent.TestingConfig() + cfg = torrent.TestingConfig(t) cfg.Seed = false cfg.DataDir, err = ioutil.TempDir("", "") require.NoError(t, err) diff --git a/test/unix_test.go b/test/unix_test.go index d021b985918fea99aeee070d5ff86368da89f612..1e877c0d5a9dd98c15dfb1daa756919fb955c7d2 100644 --- a/test/unix_test.go +++ b/test/unix_test.go @@ -25,7 +25,7 @@ cfg.Debug = true }, Client: func(cl *torrent.Client) { cl.AddDialer(torrent.NetDialer{Network: "unix"}) - l, err := net.Listen("unix", filepath.Join(torrent.TestingTempDir.NewSub(), "socket")) + l, err := net.Listen("unix", filepath.Join(t.TempDir(), "socket")) if err != nil { panic(err) } diff --git a/testdata/The WIRED CD - Rip. Sample. Mash. Share.torrent b/testdata/The WIRED CD - Rip. Sample. Mash. Share.torrent new file mode 100644 index 0000000000000000000000000000000000000000..e5acba508544ddc46186a3e90eb4ebc0b608d2c7 Binary files /dev/null and b/testdata/The WIRED CD - Rip. Sample. Mash. Share.torrent differ diff --git a/testdata/debian-10.8.0-amd64-netinst.iso.torrent b/testdata/debian-10.8.0-amd64-netinst.iso.torrent new file mode 100644 index 0000000000000000000000000000000000000000..24e6728b4bf179f6a9d153e21fe0ab490c7600c0 Binary files /dev/null and b/testdata/debian-10.8.0-amd64-netinst.iso.torrent differ diff --git a/testing.go b/testing.go index facf68a97a70e9ab03d1a83a1489c766185f925f..01e728578fb902fb0ab7325b906431fdd09a0452 100644 --- a/testing.go +++ b/testing.go @@ -1,16 +1,14 @@ package torrent import ( - "github.com/anacrolix/torrent/internal/tmproot" + "testing" ) -var TestingTempDir tmproot.Dir - -func TestingConfig() *ClientConfig { +func TestingConfig(t testing.TB) *ClientConfig { cfg := NewDefaultClientConfig() cfg.ListenHost = LoopbackListenHost cfg.NoDHT = true - cfg.DataDir = TestingTempDir.NewSub() + cfg.DataDir = t.TempDir() cfg.DisableTrackers = true cfg.NoDefaultPortForwarding = true cfg.DisableAcceptRateLimiting = true diff --git a/torrent.go b/torrent.go index a4ae4b48c4bb340a310edefa1a67a304038e34e6..ac70df6f81b0d248252c4ac8ba5658188582f617 100644 --- a/torrent.go +++ b/torrent.go @@ -735,9 +735,9 @@ t.storageLock.Lock() t.storage.Close() t.storageLock.Unlock() } - for conn := range t.conns { - conn.close() - } + t.iterPeers(func(p *Peer) { + p.close() + }) t.pex.Reset() t.cl.event.Broadcast() t.pieceStateChanges.Close() diff --git a/torrent_test.go b/torrent_test.go index b40650391d2e7c01e0636ed959797f5be349b620..d53aa693a4b8f7a53ea9860691560889513a41ae 100644 --- a/torrent_test.go +++ b/torrent_test.go @@ -81,7 +81,7 @@ const ( numPieces = 13410 pieceLength = 256 << 10 ) - cl := &Client{config: TestingConfig()} + cl := &Client{config: TestingConfig(b)} cl.initLogger() t := cl.newTorrent(metainfo.Hash{}, nil) require.NoError(b, t.setInfo(&metainfo.Info{ @@ -132,7 +132,7 @@ assert.True(t, missinggo.FilePathExists(fp)) } func TestEmptyFilesAndZeroPieceLengthWithFileStorage(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) ci := storage.NewFile(cfg.DataDir) defer ci.Close() cfg.DefaultStorage = ci @@ -140,7 +140,7 @@ testEmptyFilesAndZeroPieceLength(t, cfg) } func TestEmptyFilesAndZeroPieceLengthWithMMapStorage(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) ci := storage.NewMMap(cfg.DataDir) defer ci.Close() cfg.DefaultStorage = ci @@ -150,7 +150,7 @@ func TestPieceHashFailed(t *testing.T) { mi := testutil.GreetingMetaInfo() cl := new(Client) - cl.config = TestingConfig() + cl.config = TestingConfig(t) cl.initLogger() tt := cl.newTorrent(mi.HashInfoBytes(), badStorage{}) tt.setChunkSize(2) @@ -166,7 +166,7 @@ } // Check the behaviour of Torrent.Metainfo when metadata is not completed. func TestTorrentMetainfoIncompleteMetadata(t *testing.T) { - cfg := TestingConfig() + cfg := TestingConfig(t) cfg.Debug = true cl, err := NewClient(cfg) require.NoError(t, err) diff --git a/webseed-peer.go b/webseed-peer.go index e436f8aab0fd54bbf9c0ee3c1ceeaa6c60a24932..e2df582ed5cbbea02278984e917d7b79743ba336 100644 --- a/webseed-peer.go +++ b/webseed-peer.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "strings" "sync" @@ -117,11 +118,18 @@ if result.Err != nil { if !errors.Is(result.Err, context.Canceled) { ws.peer.logger.Printf("Request %v rejected: %v", r, result.Err) } - // Always close for now. We need to filter out temporary errors, but this is a nightmare in - // Go. Currently a bad webseed URL can starve out the good ones due to the chunk selection - // algorithm. + // We need to filter out temporary errors, but this is a nightmare in Go. Currently a bad + // webseed URL can starve out the good ones due to the chunk selection algorithm. const closeOnAllErrors = false - if closeOnAllErrors || strings.Contains(result.Err.Error(), "unsupported protocol scheme") { + if closeOnAllErrors || + strings.Contains(result.Err.Error(), "unsupported protocol scheme") || + func() bool { + var err webseed.ErrBadResponse + if !errors.As(result.Err, &err) { + return false + } + return err.Response.StatusCode == http.StatusNotFound + }() { ws.peer.close() } else { ws.peer.remoteRejectedRequest(r) diff --git a/webseed/client.go b/webseed/client.go index 420901ea761e678d4e8bdbc6a40c415d76d5c219..cc17b339343daa2be6ed23e11809248f6d530030 100644 --- a/webseed/client.go +++ b/webseed/client.go @@ -3,7 +3,6 @@ import ( "bytes" "context" - "errors" "fmt" "io" "net/http" @@ -86,6 +85,15 @@ }() return req } +type ErrBadResponse struct { + Msg string + Response *http.Response +} + +func (me ErrBadResponse) Error() string { + return me.Msg +} + func recvPartResult(buf io.Writer, part requestPart) error { result := <-part.result if result.err != nil { @@ -96,10 +104,13 @@ switch result.resp.StatusCode { case http.StatusPartialContent: case http.StatusOK: if part.e.Start != 0 { - return errors.New("got status ok but request was at offset") + return ErrBadResponse{"got status ok but request was at offset", result.resp} } default: - return fmt.Errorf("unhandled response status code (%v)", result.resp.StatusCode) + return ErrBadResponse{ + fmt.Sprintf("unhandled response status code (%v)", result.resp.StatusCode), + result.resp, + } } copied, err := io.Copy(buf, result.resp.Body) if err != nil {