]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Don't assign DisallowData* being assigned in Torrent.MergeSpec
authorMatt Joiner <anacrolix@gmail.com>
Tue, 12 Aug 2025 13:40:59 +0000 (23:40 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Tue, 12 Aug 2025 13:40:59 +0000 (23:40 +1000)
This was masking an ancient bug in SetInfoBytes, and AllowDataDownload!

client.go
spec.go
storage/file-io-classic.go [new file with mode: 0644]
storage/file-io-common.go [new file with mode: 0644]
storage/file-io-mmap.go [new file with mode: 0644]

index 76f986aaebe5c54e862876d441d394e3260ef2b7..f2b4aefd590a4490e8c017dc9d18484b73ab6789 100644 (file)
--- a/client.go
+++ b/client.go
@@ -1530,6 +1530,9 @@ type AddTorrentOpts struct {
        // Require pieces to be checked as soon as info is available. This is because we have no way to
        // schedule an initial check only, and don't want to race against use of Torrent.Complete.
        IgnoreUnverifiedPieceCompletion bool
+       // Whether to initially allow data download or upload
+       DisallowDataUpload   bool
+       DisallowDataDownload bool
 }
 
 // Add or merge a torrent spec. Returns new if the torrent wasn't already in the client. See also
@@ -1547,8 +1550,9 @@ func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err e
 }
 
 // The trackers will be merged with the existing ones. If the Info isn't yet known, it will be set.
-// spec.DisallowDataDownload/Upload will be read and applied
 // The display name is replaced if the new spec provides one. Note that any `Storage` is ignored.
+// Many fields in the AddTorrentOpts field in TorrentSpec are ignored because the Torrent is already
+// added.
 func (t *Torrent) MergeSpec(spec *TorrentSpec) error {
        if spec.DisplayName != "" {
                t.SetDisplayName(spec.DisplayName)
@@ -1580,8 +1584,6 @@ func (t *Torrent) MergeSpec(spec *TorrentSpec) error {
        }
        t.addTrackers(spec.Trackers)
        t.maybeNewConns()
-       t.dataDownloadDisallowed.SetBool(spec.DisallowDataDownload)
-       t.dataUploadDisallowed = spec.DisallowDataUpload
        return errors.Join(t.addPieceLayersLocked(spec.PieceLayers)...)
 }
 
diff --git a/spec.go b/spec.go
index e8381943179795c152f46a0b8594780205897fed..27274c5eeb0954a6e33aa0eb5f4b96441d052dd2 100644 (file)
--- a/spec.go
+++ b/spec.go
@@ -27,10 +27,6 @@ type TorrentSpec struct {
        Sources []string
        // BEP 52 "piece layers" from metainfo
        PieceLayers map[string]string
-
-       // Whether to allow data download or upload
-       DisallowDataUpload   bool
-       DisallowDataDownload bool
 }
 
 func TorrentSpecFromMagnetUri(uri string) (spec *TorrentSpec, err error) {
diff --git a/storage/file-io-classic.go b/storage/file-io-classic.go
new file mode 100644 (file)
index 0000000..9a36ae9
--- /dev/null
@@ -0,0 +1,28 @@
+package storage
+
+import (
+       "os"
+)
+
+type classicFileIo struct{}
+
+func (i classicFileIo) openForSharedRead(name string) (sharedFileIf, error) {
+       return sharedFiles.Open(name)
+}
+
+type classicFileReader struct {
+       *os.File
+}
+
+func (c classicFileReader) seekData(offset int64) (ret int64, err error) {
+       return seekData(c.File, offset)
+}
+
+func (i classicFileIo) openForRead(name string) (fileReader, error) {
+       f, err := os.Open(name)
+       return classicFileReader{f}, err
+}
+
+func (classicFileIo) openForWrite(p string, size int64) (f fileWriter, err error) {
+       return openFileExtra(p, os.O_WRONLY)
+}
diff --git a/storage/file-io-common.go b/storage/file-io-common.go
new file mode 100644 (file)
index 0000000..ffc87cc
--- /dev/null
@@ -0,0 +1,35 @@
+package storage
+
+import (
+       "errors"
+       "io/fs"
+       "os"
+       "path/filepath"
+
+       "github.com/anacrolix/missinggo/v2/panicif"
+)
+
+// Opens file for write, creating dirs and fixing permissions as necessary.
+func openFileExtra(p string, osRdwr int) (f *os.File, err error) {
+       panicif.NotZero(osRdwr & ^(os.O_RDONLY | os.O_RDWR | os.O_WRONLY))
+       flag := osRdwr | os.O_CREATE
+       f, err = os.OpenFile(p, flag, filePerm)
+       if err == nil {
+               return
+       }
+       if errors.Is(err, fs.ErrNotExist) {
+               err = os.MkdirAll(filepath.Dir(p), dirPerm)
+               if err != nil {
+                       return
+               }
+       } else if errors.Is(err, fs.ErrPermission) {
+               err = os.Chmod(p, filePerm)
+               if err != nil {
+                       return
+               }
+       } else {
+               return
+       }
+       f, err = os.OpenFile(p, flag, filePerm)
+       return
+}
diff --git a/storage/file-io-mmap.go b/storage/file-io-mmap.go
new file mode 100644 (file)
index 0000000..af070cb
--- /dev/null
@@ -0,0 +1,201 @@
+package storage
+
+import (
+       "fmt"
+       "io"
+       "io/fs"
+       "os"
+       "sync"
+       "sync/atomic"
+
+       g "github.com/anacrolix/generics"
+       "github.com/anacrolix/missinggo/v2/panicif"
+       "github.com/edsrzf/mmap-go"
+)
+
+type mmapFileIo struct {
+       mu    sync.Mutex
+       paths map[string]*fileMmap
+}
+
+type fileMmap struct {
+       m        mmap.MMap
+       writable bool
+       refs     atomic.Int32
+}
+
+func (me *fileMmap) dec() error {
+       if me.refs.Add(-1) == 0 {
+               return me.m.Unmap()
+       }
+       return nil
+}
+
+func (me *fileMmap) inc() {
+       panicif.LessThanOrEqual(me.refs.Add(1), 0)
+}
+
+func (m *mmapFileIo) openForSharedRead(name string) (_ sharedFileIf, err error) {
+       return m.openReadOnly(name)
+}
+
+func (m *mmapFileIo) openForRead(name string) (_ fileReader, err error) {
+       sh, err := m.openReadOnly(name)
+       if err != nil {
+               return
+       }
+       return &mmapFileHandle{
+               shared: sh,
+       }, nil
+}
+
+func (m *mmapFileIo) openReadOnly(name string) (_ *mmapSharedFileHandle, err error) {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+       v, ok := m.paths[name]
+       if ok {
+               return newMmapFile(v), nil
+       }
+       f, err := os.Open(name)
+       if err != nil {
+               return
+       }
+       defer f.Close()
+       mm, err := mmap.Map(f, mmap.RDONLY, 0)
+       if err != nil {
+               err = fmt.Errorf("mapping file: %w", err)
+               return
+       }
+       v = m.addNewMmap(name, mm, false)
+       return newMmapFile(v), nil
+}
+
+func (m *mmapFileIo) openForWrite(name string, size int64) (_ fileWriter, err error) {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+       v, ok := m.paths[name]
+       if ok {
+               if int64(len(v.m)) == size && v.writable {
+                       v.inc()
+                       return newMmapFile(v), nil
+               } else {
+                       v.dec()
+                       g.MustDelete(m.paths, name)
+               }
+       }
+       f, err := openFileExtra(name, os.O_RDWR)
+       if err != nil {
+               return
+       }
+       defer f.Close()
+       err = f.Truncate(size)
+       if err != nil {
+               err = fmt.Errorf("error truncating file: %w", err)
+               return
+       }
+       mm, err := mmap.Map(f, mmap.RDWR, 0)
+       if err != nil {
+               return
+       }
+       // This can happen due to filesystem changes outside our control. Don't be naive.
+       if int64(len(mm)) != size {
+               err = fmt.Errorf("new mmap has wrong size %v, expected %v", len(mm), size)
+               mm.Unmap()
+               return
+       }
+       return newMmapFile(m.addNewMmap(name, mm, true)), nil
+}
+
+func newMmapFile(f *fileMmap) *mmapSharedFileHandle {
+       ret := &mmapSharedFileHandle{
+               f: f,
+       }
+       ret.f.inc()
+       return ret
+}
+
+func (me *mmapFileIo) addNewMmap(name string, mm mmap.MMap, writable bool) *fileMmap {
+       v := &fileMmap{
+               m:        mm,
+               writable: writable,
+       }
+       // One for the store, one for the caller.
+       v.refs.Store(1)
+       g.MakeMapIfNil(&me.paths)
+       g.MapMustAssignNew(me.paths, name, v)
+       return v
+}
+
+var _ fileIo = (*mmapFileIo)(nil)
+
+type mmapSharedFileHandle struct {
+       f     *fileMmap
+       close sync.Once
+}
+
+func (m *mmapSharedFileHandle) WriteAt(p []byte, off int64) (n int, err error) {
+       //fmt.Println("mmapSharedFileHandle.WriteAt", off, len(p), len(m.f.m))
+       n = copy(m.f.m[off:], p)
+       return
+}
+
+func (m *mmapSharedFileHandle) WriteTo(w io.Writer) (n int64, err error) {
+       //TODO implement me
+       panic("implement me")
+}
+
+func (m *mmapSharedFileHandle) ReadAt(p []byte, off int64) (n int, err error) {
+       n = copy(p, m.f.m[off:])
+       if n < len(p) {
+               if off < 0 {
+                       err = fs.ErrInvalid
+                       return
+               }
+       }
+       if off+int64(n) == int64(len(m.f.m)) {
+               err = io.EOF
+       }
+       return
+}
+
+func (m *mmapSharedFileHandle) Close() (err error) {
+       m.close.Do(func() {
+               err = m.f.dec()
+       })
+       return
+}
+
+type mmapFileHandle struct {
+       shared *mmapSharedFileHandle
+       pos    int64
+}
+
+func (me *mmapFileHandle) WriteTo(w io.Writer) (n int64, err error) {
+       n1, err := w.Write(me.shared.f.m[me.pos:])
+       n = int64(n1)
+       me.pos += n
+       return
+}
+
+func (me *mmapFileHandle) Close() error {
+       return me.shared.Close()
+}
+
+func (me *mmapFileHandle) Read(p []byte) (n int, err error) {
+       if me.pos > int64(len(me.shared.f.m)) {
+               err = io.EOF
+               return
+       }
+       n = copy(p, me.shared.f.m[me.pos:])
+       me.pos += int64(n)
+       if me.pos >= int64(len(me.shared.f.m)) {
+               err = io.EOF
+       }
+       return
+}
+
+func (me *mmapFileHandle) seekData(offset int64) (ret int64, err error) {
+       me.pos = offset
+       ret = offset
+       return
+}