fix(upload): retry on O_EXCL collision in single-file upload (B12)
This commit is contained in:
@@ -135,23 +135,40 @@ func (h *H) Upload(c *gin.Context) {
|
||||
err(c, http.StatusBadRequest, "bad_format", "extension must be cbz/pdf/epub/txt/md")
|
||||
return
|
||||
}
|
||||
dst, e := h.uniquePath(root, name)
|
||||
if e != nil {
|
||||
err(c, http.StatusForbidden, "forbidden", e.Error())
|
||||
// B12: retry on O_EXCL collision — concurrent uploads with the same name
|
||||
// can both get the same candidate from uniquePath (stat-then-create race).
|
||||
var dst, tmp string
|
||||
var out *os.File
|
||||
for attempt := 0; attempt < 5; attempt++ {
|
||||
var e error
|
||||
dst, e = h.uniquePath(root, name)
|
||||
if e != nil {
|
||||
err(c, http.StatusForbidden, "forbidden", e.Error())
|
||||
return
|
||||
}
|
||||
tmp = dst + ".upload-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
out, e = os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
if !os.IsExist(e) {
|
||||
err(c, http.StatusInternalServerError, "internal", "create tmp")
|
||||
return
|
||||
}
|
||||
// O_EXCL collision — retry with fresh uniquePath.
|
||||
}
|
||||
if out == nil {
|
||||
err(c, http.StatusConflict, "conflict", "too many concurrent uploads with same name")
|
||||
return
|
||||
}
|
||||
src, e := fh.Open()
|
||||
if e != nil {
|
||||
out.Close()
|
||||
os.Remove(tmp)
|
||||
err(c, http.StatusInternalServerError, "internal", "open upload")
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
tmp := dst + ".upload-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
out, e := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
|
||||
if e != nil {
|
||||
err(c, http.StatusInternalServerError, "internal", "create tmp")
|
||||
return
|
||||
}
|
||||
// B6: only MaxBytesError returns 413; other io.Copy failures (disk full,
|
||||
// connection drop) return 500.
|
||||
if _, e := io.Copy(out, src); e != nil {
|
||||
|
||||
Reference in New Issue
Block a user