Skip to content

Commit 1172d7e

Browse files
Merge pull request #160 from Enrico204/reply-with-insufficient-storage-on-disk-full
Reply "insufficient storage" on disk full or over-quota
2 parents 8729a69 + fb5d634 commit 1172d7e

File tree

4 files changed

+39
-5
lines changed

4 files changed

+39
-5
lines changed

changelog/unreleased/pull-160

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Bugfix: Reply "insufficient storage" on disk full or over-quota
2+
3+
When there was no space left on disk, or any other write-related error,
4+
rest-server was replying with HTTP status code 400 (Bad request). This is
5+
misleading (restic client will dump the status code to the user).
6+
7+
This has been fixed changing the behaviour so two different statuses are used:
8+
* HTTP 507 "Insufficient storage" is the status on disk full or repository
9+
over-quota
10+
* HTTP 500 "Internal server error" on other disk-related errors
11+
12+
https://github.com/restic/rest-server/issues/155
13+
https://github.com/restic/rest-server/pull/160

handlers_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"crypto/rand"
66
"encoding/hex"
7-
"errors"
87
"fmt"
98
"io"
109
"io/ioutil"
@@ -398,7 +397,7 @@ func TestAbortedRequest(t *testing.T) {
398397

399398
// the first request is an upload to a file which blocks while reading the
400399
// body and then after some data returns an error
401-
rd := newDelayedErrorReader(errors.New("injected"))
400+
rd := newDelayedErrorReader(io.ErrUnexpectedEOF)
402401

403402
wg.Add(1)
404403
go func() {

quota/quota.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (m *Manager) WrapWriter(req *http.Request, w io.Writer) (io.Writer, int, er
7575
if currentSize+contentLen > m.maxRepoSize {
7676
err := fmt.Errorf("incoming blob (%d bytes) would exceed maximum size of repository (%d bytes)",
7777
contentLen, m.maxRepoSize)
78-
return nil, http.StatusRequestEntityTooLarge, err
78+
return nil, http.StatusInsufficientStorage, err
7979
}
8080
}
8181

repo/repo.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package repo
33
import (
44
"encoding/hex"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"io/ioutil"
@@ -13,6 +14,7 @@ import (
1314
"regexp"
1415
"runtime"
1516
"strings"
17+
"syscall"
1618
"time"
1719

1820
"github.com/minio/sha256-simd"
@@ -83,6 +85,10 @@ func httpMethodNotAllowed(w http.ResponseWriter, allowed []string) {
8385
httpDefaultError(w, http.StatusMethodNotAllowed)
8486
}
8587

88+
// errFileContentDoesntMatchHash is the error raised when the file content hash
89+
// doesn't match the hash provided in the URL
90+
var errFileContentDoesntMatchHash = errors.New("file content does not match hash")
91+
8692
// BlobPathRE matches valid blob URI paths with optional object IDs
8793
var BlobPathRE = regexp.MustCompile(`^/(data|index|keys|locks|snapshots)/([0-9a-f]{64})?$`)
8894

@@ -592,7 +598,7 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
592598

593599
// reject if file content doesn't match file name
594600
if err == nil && hex.EncodeToString(hasher.Sum(nil)) != objectID {
595-
err = fmt.Errorf("file content does not match hash")
601+
err = errFileContentDoesntMatchHash
596602
}
597603
}
598604

@@ -603,7 +609,23 @@ func (h *Handler) saveBlob(w http.ResponseWriter, r *http.Request) {
603609
if h.opt.Debug {
604610
log.Print(err)
605611
}
606-
httpDefaultError(w, http.StatusBadRequest)
612+
var pathError *os.PathError
613+
if errors.As(err, &pathError) && (pathError.Err == syscall.ENOSPC ||
614+
pathError.Err == syscall.EDQUOT) {
615+
// The error is disk-related (no space left, no quota left),
616+
// notify the client using the correct HTTP status
617+
httpDefaultError(w, http.StatusInsufficientStorage)
618+
} else if errors.Is(err, errFileContentDoesntMatchHash) ||
619+
errors.Is(err, io.ErrUnexpectedEOF) ||
620+
errors.Is(err, http.ErrMissingBoundary) ||
621+
errors.Is(err, http.ErrNotMultipart) {
622+
// The error is connection-related, send a client-side HTTP status
623+
httpDefaultError(w, http.StatusBadRequest)
624+
} else {
625+
// Otherwise we have a different internal error, reply with
626+
// server-side HTTP status
627+
h.internalServerError(w, err)
628+
}
607629
return
608630
}
609631

0 commit comments

Comments
 (0)