mirror of
https://github.com/restic/rest-server.git
synced 2025-10-19 15:43:21 +00:00
Atomic upload for blobs
A upload is now first saved to a temporary file before it gets renamed to the final filename. This ensures that incomplete uploads don't leave broken files behind (at least not under their real filename). In addition, removing failed uploads is no longer prone to a race condition with a retried upload. That scenario could occur when the first upload fails partway and the server doesn't notice that immediately. A later retry by restic will then delete the broken upload and upload the file again. If the server notices now that the initial upload has failed, then it will delete the correctly uploaded file. This has been fixed by only ever deleting the temporary file during upload.
This commit is contained in:
parent
4db46a5d3d
commit
82816c67e1
1 changed files with 22 additions and 9 deletions
31
repo/repo.go
31
repo/repo.go
|
@ -542,7 +542,17 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
path := h.getObjectPath(objectType, objectID)
|
||||
|
||||
tf, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.FileMode)
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
httpDefaultError(w, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile(filepath.Dir(path), ".rest-server-temp")
|
||||
if os.IsNotExist(err) {
|
||||
// the error is caused by a missing directory, create it and retry
|
||||
mkdirErr := os.MkdirAll(filepath.Dir(path), h.opt.DirMode)
|
||||
|
@ -550,13 +560,9 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
|||
log.Print(mkdirErr)
|
||||
} else {
|
||||
// try again
|
||||
tf, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, h.opt.FileMode)
|
||||
tf, err = ioutil.TempFile(filepath.Dir(path), ".rest-server-temp")
|
||||
}
|
||||
}
|
||||
if os.IsExist(err) {
|
||||
httpDefaultError(w, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
|
@ -590,7 +596,7 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if err != nil {
|
||||
_ = tf.Close()
|
||||
_ = os.Remove(path)
|
||||
_ = os.Remove(tf.Name())
|
||||
h.incrementRepoSpaceUsage(-written)
|
||||
if h.opt.Debug {
|
||||
log.Print(err)
|
||||
|
@ -601,14 +607,21 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if err := tf.Sync(); err != nil {
|
||||
_ = tf.Close()
|
||||
_ = os.Remove(path)
|
||||
_ = os.Remove(tf.Name())
|
||||
h.incrementRepoSpaceUsage(-written)
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tf.Close(); err != nil {
|
||||
_ = os.Remove(path)
|
||||
_ = os.Remove(tf.Name())
|
||||
h.incrementRepoSpaceUsage(-written)
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Rename(tf.Name(), path); err != nil {
|
||||
_ = os.Remove(tf.Name())
|
||||
h.incrementRepoSpaceUsage(-written)
|
||||
h.internalServerError(w, err)
|
||||
return
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue