From 041a9510c671ecf4724bf15388fdb9de549b04ed Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 8 Feb 2016 15:40:12 -0800 Subject: [PATCH] Fixing 'docker save' on Windows. Save was failing file integrity checksums due to bugs in both Windows and Docker. This commit includes fixes to file time handling in tarexport and system.chtimes that are necessary along with the Windows platform fixes to correctly support save. With this change, sysfile_backups for windowsfilter driver are no longer needed, so that code is removed. Signed-off-by: Stefan J. Wernli --- daemon/graphdriver/windows/windows.go | 65 --------------------------- image/tarexport/save.go | 9 ++-- pkg/archive/changes_test.go | 8 ++-- pkg/system/chtimes.go | 5 +++ pkg/system/chtimes_unix.go | 14 ++++++ pkg/system/chtimes_unix_test.go | 2 +- pkg/system/chtimes_windows.go | 27 +++++++++++ 7 files changed, 57 insertions(+), 73 deletions(-) create mode 100644 pkg/system/chtimes_unix.go create mode 100644 pkg/system/chtimes_windows.go diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index 7465dc392a..77f4f1b774 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/daemon/graphdriver/windows/windows.go @@ -6,7 +6,6 @@ import ( "crypto/sha512" "encoding/json" "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -501,10 +500,6 @@ func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPat if size, err = chrootarchive.ApplyLayer(tempFolder, layerData); err != nil { return } - err = copySysFiles(tempFolder, filepath.Join(d.info.HomeDir, "sysfile-backups", id)) - if err != nil { - return - } logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) if err = hcsshim.ImportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil { @@ -602,69 +597,9 @@ func (d *Driver) DiffPath(id string) (path string, release func() error, err err return } - err = copySysFiles(filepath.Join(d.info.HomeDir, "sysfile-backups", id), tempFolder) - if err != nil { - return - } - return tempFolder, func() error { // TODO: activate layers and release here? _, folderName := filepath.Split(tempFolder) return hcsshim.DestroyLayer(d.info, folderName) }, nil } - -var sysFileWhiteList = []string{ - "Hives\\*", - "Files\\BOOTNXT", - "tombstones.txt", -} - -// note this only handles files -func copySysFiles(src string, dest string) error { - if err := os.MkdirAll(dest, 0700); err != nil { - return err - } - return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { - rel, err := filepath.Rel(src, path) - if err != nil { - return err - } - for _, sysfile := range sysFileWhiteList { - if matches, err := filepath.Match(sysfile, rel); err != nil || !matches { - continue - } - - fi, err := os.Lstat(path) - if err != nil { - return err - } - - if !fi.Mode().IsRegular() { - continue - } - - targetPath := filepath.Join(dest, rel) - if err = os.MkdirAll(filepath.Dir(targetPath), 0700); err != nil { - return err - } - - in, err := os.Open(path) - if err != nil { - return err - } - out, err := os.Create(targetPath) - if err != nil { - in.Close() - return err - } - _, err = io.Copy(out, in) - in.Close() - out.Close() - if err != nil { - return err - } - } - return nil - }) -} diff --git a/image/tarexport/save.go b/image/tarexport/save.go index b7022ac5d8..45258e4f85 100644 --- a/image/tarexport/save.go +++ b/image/tarexport/save.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/image/v1" "github.com/docker/docker/layer" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/reference" ) @@ -160,7 +161,7 @@ func (s *saveSession) save(outStream io.Writer) error { if err := f.Close(); err != nil { return err } - if err := os.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { + if err := system.Chtimes(reposFile, time.Unix(0, 0), time.Unix(0, 0)); err != nil { return err } } @@ -177,7 +178,7 @@ func (s *saveSession) save(outStream io.Writer) error { if err := f.Close(); err != nil { return err } - if err := os.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil { + if err := system.Chtimes(manifestFileName, time.Unix(0, 0), time.Unix(0, 0)); err != nil { return err } @@ -233,7 +234,7 @@ func (s *saveSession) saveImage(id image.ID) error { if err := ioutil.WriteFile(configFile, img.RawJSON(), 0644); err != nil { return err } - if err := os.Chtimes(configFile, img.Created, img.Created); err != nil { + if err := system.Chtimes(configFile, img.Created, img.Created); err != nil { return err } @@ -290,7 +291,7 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat for _, fname := range []string{"", legacyVersionFileName, legacyConfigFileName, legacyLayerFileName} { // todo: maybe save layer created timestamp? - if err := os.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { + if err := system.Chtimes(filepath.Join(outDir, fname), createdTime, createdTime); err != nil { return err } } diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go index f4316ce21e..00bd69f31e 100644 --- a/pkg/archive/changes_test.go +++ b/pkg/archive/changes_test.go @@ -8,6 +8,8 @@ import ( "sort" "testing" "time" + + "github.com/docker/docker/pkg/system" ) func max(x, y int) int { @@ -87,7 +89,7 @@ func createSampleDir(t *testing.T, root string) { if info.filetype != Symlink { // Set a consistent ctime, atime for all files and dirs - if err := os.Chtimes(p, now, now); err != nil { + if err := system.Chtimes(p, now, now); err != nil { t.Fatal(err) } } @@ -289,7 +291,7 @@ func mutateSampleDir(t *testing.T, root string) { } // Touch file - if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { + if err := system.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { t.Fatal(err) } @@ -333,7 +335,7 @@ func mutateSampleDir(t *testing.T, root string) { } // Touch dir - if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { + if err := system.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil { t.Fatal(err) } } diff --git a/pkg/system/chtimes.go b/pkg/system/chtimes.go index acf3f566f7..7637f12e1a 100644 --- a/pkg/system/chtimes.go +++ b/pkg/system/chtimes.go @@ -43,5 +43,10 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { return err } + // Take platform specific action for setting create time. + if err := setCTime(name, mtime); err != nil { + return err + } + return nil } diff --git a/pkg/system/chtimes_unix.go b/pkg/system/chtimes_unix.go new file mode 100644 index 0000000000..09d58bcbfd --- /dev/null +++ b/pkg/system/chtimes_unix.go @@ -0,0 +1,14 @@ +// +build !windows + +package system + +import ( + "time" +) + +//setCTime will set the create time on a file. On Unix, the create +//time is updated as a side effect of setting the modified time, so +//no action is required. +func setCTime(path string, ctime time.Time) error { + return nil +} diff --git a/pkg/system/chtimes_unix_test.go b/pkg/system/chtimes_unix_test.go index fcd5940238..0aafe1d845 100644 --- a/pkg/system/chtimes_unix_test.go +++ b/pkg/system/chtimes_unix_test.go @@ -1,4 +1,4 @@ -// +build linux freebsd +// +build !windows package system diff --git a/pkg/system/chtimes_windows.go b/pkg/system/chtimes_windows.go new file mode 100644 index 0000000000..2945868465 --- /dev/null +++ b/pkg/system/chtimes_windows.go @@ -0,0 +1,27 @@ +// +build windows + +package system + +import ( + "syscall" + "time" +) + +//setCTime will set the create time on a file. On Windows, this requires +//calling SetFileTime and explicitly including the create time. +func setCTime(path string, ctime time.Time) error { + ctimespec := syscall.NsecToTimespec(ctime.UnixNano()) + pathp, e := syscall.UTF16PtrFromString(path) + if e != nil { + return e + } + h, e := syscall.CreateFile(pathp, + syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil, + syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + if e != nil { + return e + } + defer syscall.Close(h) + c := syscall.NsecToFiletime(syscall.TimespecToNsec(ctimespec)) + return syscall.SetFileTime(h, &c, nil, nil) +}