httpdl: fix progress-reporter race on name/out; harden mirror & resume validation
Findings from a Rob-Pike-lens review (bugs/races/network), each verified against the code before fixing: - httpdl: name/out are now atomic.Pointer[string] -- the engine publishes a download to the reporter before Run resolves the name, so Stat raced the write (bt already did this) - httpdl: a malformed --proxy fails loudly instead of silently bypassing it - httpdl: a 206 must carry a matching Content-Range; a 200 in segmented mode is fatal so it fails over instead of burning the retry budget - httpdl: single-stream mirror failover validates the range before appending; ErrTooSlow only when the error is a real ctx cancellation - httpdl: idle guard tracks progress by timestamp (no Reset/Stop race, no sticky fired flag) - httpdl/control: reject a resume file whose segments don't tile [0,total) - bt: clamp the listen-port range; verify on-disk data before choosing pieces under --check-integrity - cli: reject size overflow; show --seed-time=MIN; clamp --select-file range - progress/engine/main: clamp ETA against int64 overflow; show queued downloads as waiting; join the reporter on exit instead of a 20ms sleep
This commit is contained in:
25
main.go
25
main.go
@@ -100,9 +100,15 @@ func run(args []string) int {
|
||||
eng := download.NewEngine(opts.Int("max-concurrent-downloads"))
|
||||
|
||||
uiCtx, uiCancel := context.WithCancel(context.Background())
|
||||
uiDone := make(chan struct{})
|
||||
if !opts.Bool("quiet") && !opts.Bool("dry-run") {
|
||||
rep := progress.New(eng.Snapshot, opts.Bool("human-readable"))
|
||||
go rep.Run(uiCtx)
|
||||
go func() {
|
||||
rep.Run(uiCtx)
|
||||
close(uiDone)
|
||||
}()
|
||||
} else {
|
||||
close(uiDone)
|
||||
}
|
||||
|
||||
jobsRef.set(jobs)
|
||||
@@ -129,7 +135,7 @@ func run(args []string) int {
|
||||
}
|
||||
|
||||
uiCancel()
|
||||
time.Sleep(20 * time.Millisecond) // let the renderer clear its line
|
||||
<-uiDone // wait for the renderer's final frame before printing the OK/FAIL lines
|
||||
|
||||
saveCookies(jobs)
|
||||
|
||||
@@ -831,6 +837,11 @@ func makeLimiter(bps int64) *rate.Limiter {
|
||||
return rate.NewLimiter(rate.Limit(bps), download.LimiterBurst(bps))
|
||||
}
|
||||
|
||||
// maxSelectIndex bounds how far a --select-file range is expanded: a torrent has
|
||||
// at most a few thousand files, so a degenerate spec like "1-9999999999" must not
|
||||
// spin or balloon the map. Indexes past the real file count are ignored anyway.
|
||||
const maxSelectIndex = 1 << 20
|
||||
|
||||
// parseSelect parses "1,3-5" into a set of 1-based file indexes.
|
||||
func parseSelect(s string) map[int]bool {
|
||||
s = strings.TrimSpace(s)
|
||||
@@ -848,10 +859,14 @@ func parseSelect(s string) map[int]bool {
|
||||
continue
|
||||
}
|
||||
b, _ := strconv.Atoi(strings.TrimSpace(hi))
|
||||
if a < 1 {
|
||||
a = 1
|
||||
}
|
||||
if b > maxSelectIndex {
|
||||
b = maxSelectIndex
|
||||
}
|
||||
for i := a; i <= b; i++ {
|
||||
if i > 0 {
|
||||
out[i] = true
|
||||
}
|
||||
out[i] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user