Simplify and comment TempFile implementation for windows

This commit is contained in:
Michael Eischer 2021-12-29 22:07:17 +01:00
parent 9e34c791c9
commit 9a3f1a9703
2 changed files with 40 additions and 41 deletions

View File

@ -0,0 +1,10 @@
Enhancement: Improve handling of temporary files on Windows
In some cases restic failed to delete temporary files causing the current
command to fail. This has been fixed by ensuring that Windows automatically
deletes the file. In addition, temporary files are only written to disk if
necessary to reduce disk writes.
https://github.com/restic/restic/issues/3465
https://github.com/restic/restic/issues/1551
https://github.com/restic/restic/pull/3610

View File

@ -1,41 +1,14 @@
package fs package fs
import ( import (
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"syscall"
"strconv" "strconv"
"sync" "strings"
"time" "time"
)
// Random number state. "golang.org/x/sys/windows"
// We generate random temporary file names so that there's a good
// chance the file doesn't exist yet - keeps the number of tries in
// TempFile to a minimum.
var rand uint32
var randmu sync.Mutex
func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
}
func nextRandom() string {
randmu.Lock()
r := rand
if r == 0 {
r = reseed()
}
r = r*1664525 + 1013904223 // constants from Numerical Recipes
rand = r
randmu.Unlock()
return strconv.Itoa(int(1e9 + r%1e9))[1:]
}
const (
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
) )
// fixpath returns an absolute path on windows, so restic can open long file // fixpath returns an absolute path on windows, so restic can open long file
@ -61,23 +34,39 @@ func fixpath(name string) string {
return name return name
} }
// TempFile creates a temporary file. // TempFile creates a temporary file which is marked as delete-on-close
func TempFile(dir, prefix string) (f *os.File, err error) { func TempFile(dir, prefix string) (f *os.File, err error) {
// slightly modified implementation of ioutil.TempFile(dir, prefix) to allow us to add
// the FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE flags.
// These provide two large benefits:
// FILE_ATTRIBUTE_TEMPORARY tells Windows to keep the file in memory only if possible
// which reduces the amount of unnecessary disk writes.
// FILE_FLAG_DELETE_ON_CLOSE instructs Windows to automatically delete the file once
// all file descriptors are closed.
if dir == "" { if dir == "" {
dir = os.TempDir() dir = os.TempDir()
} }
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE) access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE)
creation := uint32(syscall.CREATE_NEW) creation := uint32(windows.CREATE_NEW)
flags := uint32(FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE) share := uint32(0) // prevent other processes from accessing the file
flags := uint32(windows.FILE_ATTRIBUTE_TEMPORARY | windows.FILE_FLAG_DELETE_ON_CLOSE)
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
path := filepath.Join(dir, prefix+nextRandom()) randSuffix := strconv.Itoa(int(1e9 + rnd.Intn(1e9)%1e9))[1:]
path := filepath.Join(dir, prefix+randSuffix)
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), access, 0, nil, creation, flags, 0) ptr, err := windows.UTF16PtrFromString(path)
if err == nil { if err != nil {
return os.NewFile(uintptr(h), path), nil return nil, err
} }
h, err := windows.CreateFile(ptr, access, share, nil, creation, flags, 0)
if os.IsExist(err) {
continue
}
return os.NewFile(uintptr(h), path), err
} }
// Proper error handling is still to do // Proper error handling is still to do