@@ -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+
1620func 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