Skip to content

Commit eac71af

Browse files
committed
error on out of space
1 parent 31fe26d commit eac71af

File tree

5 files changed

+924
-4
lines changed

5 files changed

+924
-4
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,4 @@ List of contributors, in chronological order:
7878
* Juan Calderon-Perez (https://github.com/gaby)
7979
* Ato Araki (https://github.com/atotto)
8080
* Roman Lebedev (https://github.com/LebedevRI)
81+
* Brian Witt (https://github.com/bwitt)

api/files.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"github.com/saracen/walker"
1414
)
1515

16+
// syncFile is a seam to allow tests to force fsync failures (e.g. ENOSPC).
17+
// In production it calls (*os.File).Sync().
18+
var syncFile = func(f *os.File) error { return f.Sync() }
19+
1620
func verifyPath(path string) bool {
1721
path = filepath.Clean(path)
1822
for _, part := range strings.Split(path, string(filepath.Separator)) {
@@ -114,34 +118,69 @@ func apiFilesUpload(c *gin.Context) {
114118
}
115119

116120
stored := []string{}
121+
openFiles := []*os.File{}
117122

123+
// Write all files first
118124
for _, files := range c.Request.MultipartForm.File {
119125
for _, file := range files {
120126
src, err := file.Open()
121127
if err != nil {
128+
// Close any files we've opened
129+
for _, f := range openFiles {
130+
_ = f.Close()
131+
}
122132
AbortWithJSONError(c, 500, err)
123133
return
124134
}
125-
defer func() { _ = src.Close() }()
126135

127136
destPath := filepath.Join(path, filepath.Base(file.Filename))
128137
dst, err := os.Create(destPath)
129138
if err != nil {
139+
_ = src.Close()
140+
// Close any files we've opened
141+
for _, f := range openFiles {
142+
_ = f.Close()
143+
}
130144
AbortWithJSONError(c, 500, err)
131145
return
132146
}
133-
defer func() { _ = dst.Close() }()
134147

135148
_, err = io.Copy(dst, src)
149+
_ = src.Close()
136150
if err != nil {
151+
_ = dst.Close()
152+
// Close any files we've opened
153+
for _, f := range openFiles {
154+
_ = f.Close()
155+
}
137156
AbortWithJSONError(c, 500, err)
138157
return
139158
}
140159

160+
// Keep file open for batch sync
161+
openFiles = append(openFiles, dst)
141162
stored = append(stored, filepath.Join(c.Params.ByName("dir"), filepath.Base(file.Filename)))
142163
}
143164
}
144165

166+
// Sync all files at once to catch ENOSPC errors
167+
for i, dst := range openFiles {
168+
err := syncFile(dst)
169+
if err != nil {
170+
// Close all files
171+
for _, f := range openFiles {
172+
_ = f.Close()
173+
}
174+
AbortWithJSONError(c, 500, fmt.Errorf("error syncing file %s: %s", stored[i], err))
175+
return
176+
}
177+
}
178+
179+
// Close all files
180+
for _, dst := range openFiles {
181+
_ = dst.Close()
182+
}
183+
145184
apiFilesUploadedCounter.WithLabelValues(c.Params.ByName("dir")).Inc()
146185
c.JSON(200, stored)
147186
}

0 commit comments

Comments
 (0)