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:
Michael Eischer 2021-01-29 18:32:33 +01:00
parent 4db46a5d3d
commit 82816c67e1

View file

@ -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