@@ -200,6 +200,8 @@ func diskSpace(loc string) (uint64, error) {
200200// moveFile moves a file by copying it and deleting the source. This is needed because os.Rename
201201// only works within one device (i.e. mountpoint). The replication server's temp location and
202202// actual location may be on different devices.
203+ // To ensure that the completed file is created atomically, the copy is done to a temp location
204+ // within the destination directory, and then the file is renamed within that destination directory.
203205func moveFile (sourcePath , destPath string ) error {
204206 // idempotent create of the destPath directory
205207 if err := os .MkdirAll (filepath .Dir (destPath ), 0755 ); err != nil {
@@ -210,16 +212,19 @@ func moveFile(sourcePath, destPath string) error {
210212 if err != nil {
211213 return fmt .Errorf ("unable to open source file: %s" , err )
212214 }
213- outputFile , err := os .Create ( destPath )
215+ outputTmp , err := os .CreateTemp ( filepath . Dir ( destPath ), "tmp-" )
214216 if err != nil {
215217 inputFile .Close ()
216- return fmt .Errorf ("unable to open dest file: %s" , err )
218+ return fmt .Errorf ("unable to create a temp file with error %s" , err )
217219 }
218- defer outputFile .Close ()
219- _ , err = io .Copy (outputFile , inputFile )
220+ _ , err = io .Copy (outputTmp , inputFile )
220221 inputFile .Close ()
221222 if err != nil {
222- return fmt .Errorf ("writing to output file failed: %s" , err )
223+ return fmt .Errorf ("writing to temp output file failed: %s" , err )
224+ }
225+ if err := os .Rename (outputTmp .Name (), destPath ); err != nil {
226+ return fmt .Errorf ("failed renaming temp file %s to output file %s with error %s" ,
227+ outputTmp .Name (), destPath , err )
223228 }
224229 // Success, now delete the original file.
225230 err = os .Remove (sourcePath )
0 commit comments