aboutsummaryrefslogtreecommitdiff
path: root/repo/repo.go
diff options
context:
space:
mode:
authorGravatar Alexander Neumann <123276+fd0@users.noreply.github.com> 2021-08-17 20:32:33 +0200
committerGravatar GitHub <noreply@github.com> 2021-08-17 20:32:33 +0200
commit05773795ddda8ee18b1c789e478839a3fc1b9fba (patch)
tree1b7a209d847c05f2b9609d2cc4dc6707ca119cb9 /repo/repo.go
parent0bd1f612d21665598cf73973eca2090397a46b5c (diff)
parent64a43228de8056cf91f9d5dc4e3dd7351f36700f (diff)
downloadrest-server-05773795ddda8ee18b1c789e478839a3fc1b9fba.tar.gz
rest-server-05773795ddda8ee18b1c789e478839a3fc1b9fba.tar.zst
rest-server-05773795ddda8ee18b1c789e478839a3fc1b9fba.zip
Merge pull request #142 from MichaelEischer/atomic-upload
Atomic file upload and directory sync
Diffstat (limited to 'repo/repo.go')
-rw-r--r--repo/repo.go57
1 files changed, 48 insertions, 9 deletions
diff --git a/repo/repo.go b/repo/repo.go
index 70e7693..01fe1d8 100644
--- a/repo/repo.go
+++ b/repo/repo.go
@@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "runtime"
"strings"
"time"
@@ -542,7 +543,18 @@ 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
+ }
+
+ tmpFn := objectID + ".rest-server-temp"
+ tf, err := ioutil.TempFile(filepath.Dir(path), tmpFn)
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 +562,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), tmpFn)
}
}
- if os.IsExist(err) {
- httpDefaultError(w, http.StatusForbidden)
- return
- }
if err != nil {
h.internalServerError(w, err)
return
@@ -590,7 +598,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,22 +609,53 @@ 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
+ }
+
+ if err := syncDir(filepath.Dir(path)); err != nil {
+ // Don't call os.Remove(path) as this is prone to race conditions with parallel upload retries
+ h.internalServerError(w, err)
+ return
+ }
+
h.sendMetric(objectType, BlobWrite, uint64(written))
}
+func syncDir(dirname string) error {
+ if runtime.GOOS == "windows" {
+ // syncing a directory is not possible on windows
+ return nil
+ }
+
+ dir, err := os.Open(dirname)
+ if err != nil {
+ return err
+ }
+ err = dir.Sync()
+ if err != nil {
+ _ = dir.Close()
+ return err
+ }
+ return dir.Close()
+}
+
// deleteBlob deletes a blob from the repository.
func (h *Handler) deleteBlob(w http.ResponseWriter, r *http.Request) {
if h.opt.Debug {