Browse code

Windows: Use sequential file access

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2017/01/12 08:55:43
Showing 4 changed files
... ...
@@ -3,16 +3,19 @@ package command
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
-	"io/ioutil"
7 6
 	"os"
8 7
 	"path/filepath"
9 8
 	"runtime"
10 9
 	"strings"
10
+
11
+	"github.com/docker/docker/pkg/system"
11 12
 )
12 13
 
13 14
 // CopyToFile writes the content of the reader to the specified file
14 15
 func CopyToFile(outfile string, r io.Reader) error {
15
-	tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
16
+	// We use sequential file access here to avoid depleting the standby list
17
+	// on Windows. On Linux, this is a call directly to ioutil.TempFile
18
+	tmpFile, err := system.TempFileSequential(filepath.Dir(outfile), ".docker_temp_")
16 19
 	if err != nil {
17 20
 		return err
18 21
 	}
... ...
@@ -825,14 +825,16 @@ func (fg *fileGetCloserWithBackupPrivileges) Get(filename string) (io.ReadCloser
825 825
 	var f *os.File
826 826
 	// Open the file while holding the Windows backup privilege. This ensures that the
827 827
 	// file can be opened even if the caller does not actually have access to it according
828
-	// to the security descriptor.
828
+	// to the security descriptor. Also use sequential file access to avoid depleting the
829
+	// standby list - Microsoft VSO Bug Tracker #9900466
829 830
 	err := winio.RunWithPrivilege(winio.SeBackupPrivilege, func() error {
830 831
 		path := longpath.AddPrefix(filepath.Join(fg.path, filename))
831 832
 		p, err := syscall.UTF16FromString(path)
832 833
 		if err != nil {
833 834
 			return err
834 835
 		}
835
-		h, err := syscall.CreateFile(&p[0], syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
836
+		const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
837
+		h, err := syscall.CreateFile(&p[0], syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|fileFlagSequentialScan, 0)
836 838
 		if err != nil {
837 839
 			return &os.PathError{Op: "open", Path: path, Err: err}
838 840
 		}
... ...
@@ -3,6 +3,7 @@
3 3
 package system
4 4
 
5 5
 import (
6
+	"io/ioutil"
6 7
 	"os"
7 8
 	"path/filepath"
8 9
 )
... ...
@@ -24,7 +25,7 @@ func IsAbs(path string) bool {
24 24
 	return filepath.IsAbs(path)
25 25
 }
26 26
 
27
-// The functions below here are wrappers for the equivalents in the os package.
27
+// The functions below here are wrappers for the equivalents in the os and ioutils packages.
28 28
 // They are passthrough on Unix platforms, and only relevant on Windows.
29 29
 
30 30
 // CreateSequential creates the named file with mode 0666 (before umask), truncating
... ...
@@ -52,3 +53,16 @@ func OpenSequential(name string) (*os.File, error) {
52 52
 func OpenFileSequential(name string, flag int, perm os.FileMode) (*os.File, error) {
53 53
 	return os.OpenFile(name, flag, perm)
54 54
 }
55
+
56
+// TempFileSequential creates a new temporary file in the directory dir
57
+// with a name beginning with prefix, opens the file for reading
58
+// and writing, and returns the resulting *os.File.
59
+// If dir is the empty string, TempFile uses the default directory
60
+// for temporary files (see os.TempDir).
61
+// Multiple programs calling TempFile simultaneously
62
+// will not choose the same file. The caller can use f.Name()
63
+// to find the pathname of the file. It is the caller's responsibility
64
+// to remove the file when no longer needed.
65
+func TempFileSequential(dir, prefix string) (f *os.File, err error) {
66
+	return ioutil.TempFile(dir, prefix)
67
+}
... ...
@@ -6,8 +6,11 @@ import (
6 6
 	"os"
7 7
 	"path/filepath"
8 8
 	"regexp"
9
+	"strconv"
9 10
 	"strings"
11
+	"sync"
10 12
 	"syscall"
13
+	"time"
11 14
 	"unsafe"
12 15
 
13 16
 	winio "github.com/Microsoft/go-winio"
... ...
@@ -234,3 +237,55 @@ func syscallOpenSequential(path string, mode int, _ uint32) (fd syscall.Handle,
234 234
 	h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
235 235
 	return h, e
236 236
 }
237
+
238
+// Helpers for TempFileSequential
239
+var rand uint32
240
+var randmu sync.Mutex
241
+
242
+func reseed() uint32 {
243
+	return uint32(time.Now().UnixNano() + int64(os.Getpid()))
244
+}
245
+func nextSuffix() string {
246
+	randmu.Lock()
247
+	r := rand
248
+	if r == 0 {
249
+		r = reseed()
250
+	}
251
+	r = r*1664525 + 1013904223 // constants from Numerical Recipes
252
+	rand = r
253
+	randmu.Unlock()
254
+	return strconv.Itoa(int(1e9 + r%1e9))[1:]
255
+}
256
+
257
+// TempFileSequential is a copy of ioutil.TempFile, modified to use sequential
258
+// file access. Below is the original comment from golang:
259
+// TempFile creates a new temporary file in the directory dir
260
+// with a name beginning with prefix, opens the file for reading
261
+// and writing, and returns the resulting *os.File.
262
+// If dir is the empty string, TempFile uses the default directory
263
+// for temporary files (see os.TempDir).
264
+// Multiple programs calling TempFile simultaneously
265
+// will not choose the same file. The caller can use f.Name()
266
+// to find the pathname of the file. It is the caller's responsibility
267
+// to remove the file when no longer needed.
268
+func TempFileSequential(dir, prefix string) (f *os.File, err error) {
269
+	if dir == "" {
270
+		dir = os.TempDir()
271
+	}
272
+
273
+	nconflict := 0
274
+	for i := 0; i < 10000; i++ {
275
+		name := filepath.Join(dir, prefix+nextSuffix())
276
+		f, err = OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
277
+		if os.IsExist(err) {
278
+			if nconflict++; nconflict > 10 {
279
+				randmu.Lock()
280
+				rand = reseed()
281
+				randmu.Unlock()
282
+			}
283
+			continue
284
+		}
285
+		break
286
+	}
287
+	return
288
+}