Przeglądaj źródła

Windows: Archive package changes for Windows daemon

Signed-off-by: John Howard <jhoward@microsoft.com>
John Howard 10 lat temu
rodzic
commit
8228ee4b0f

+ 17 - 31
pkg/archive/archive.go

@@ -12,8 +12,8 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"path"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"syscall"
 
@@ -291,17 +291,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
 		file.Close()
 
 	case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
-		mode := uint32(hdr.Mode & 07777)
-		switch hdr.Typeflag {
-		case tar.TypeBlock:
-			mode |= syscall.S_IFBLK
-		case tar.TypeChar:
-			mode |= syscall.S_IFCHR
-		case tar.TypeFifo:
-			mode |= syscall.S_IFIFO
-		}
-
-		if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+		// Handle this is an OS-specific way
+		if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
 			return err
 		}
 
@@ -337,8 +328,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
 		return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
 	}
 
-	if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
-		return err
+	// Lchown is not supported on Windows
+	if runtime.GOOS != "windows" {
+		if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
+			return err
+		}
 	}
 
 	for key, value := range hdr.Xattrs {
@@ -349,20 +343,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
 
 	// There is no LChmod, so ignore mode for symlink. Also, this
 	// must happen after chown, as that can modify the file mode
-	if hdr.Typeflag == tar.TypeLink {
-		if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
-			if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
-				return err
-			}
-		}
-	} else if hdr.Typeflag != tar.TypeSymlink {
-		if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
-			return err
-		}
+	if err := handleLChmod(hdr, path, hdrInfo); err != nil {
+		return err
 	}
 
 	ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
-	// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
+	// syscall.UtimesNano doesn't support a NOFOLLOW flag atm
 	if hdr.Typeflag == tar.TypeLink {
 		if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
 			if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
@@ -531,7 +517,7 @@ loop:
 			parent := filepath.Dir(hdr.Name)
 			parentPath := filepath.Join(dest, parent)
 			if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
-				err = os.MkdirAll(parentPath, 0777)
+				err = system.MkdirAll(parentPath, 0777)
 				if err != nil {
 					return err
 				}
@@ -651,7 +637,7 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error {
 	}
 	// Create dst, copy src's content into it
 	logrus.Debugf("Creating dest directory: %s", dst)
-	if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
+	if err := system.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
 		return err
 	}
 	logrus.Debugf("Calling TarUntar(%s, %s)", src, dst)
@@ -675,12 +661,12 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
 	if srcSt.IsDir() {
 		return fmt.Errorf("Can't copy a directory")
 	}
-	// Clean up the trailing /
-	if dst[len(dst)-1] == '/' {
-		dst = path.Join(dst, filepath.Base(src))
+	// Clean up the trailing slash
+	if dst[len(dst)-1] == os.PathSeparator {
+		dst = filepath.Join(dst, filepath.Base(src))
 	}
 	// Create the holding directory if necessary
-	if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
+	if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
 		return err
 	}
 

+ 36 - 0
pkg/archive/archive_unix.go

@@ -7,6 +7,8 @@ import (
 	"errors"
 	"os"
 	"syscall"
+
+	"github.com/docker/docker/pkg/system"
 )
 
 // canonicalTarNameForPath returns platform-specific filepath
@@ -51,3 +53,37 @@ func major(device uint64) uint64 {
 func minor(device uint64) uint64 {
 	return (device & 0xff) | ((device >> 12) & 0xfff00)
 }
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+	mode := uint32(hdr.Mode & 07777)
+	switch hdr.Typeflag {
+	case tar.TypeBlock:
+		mode |= syscall.S_IFBLK
+	case tar.TypeChar:
+		mode |= syscall.S_IFCHR
+	case tar.TypeFifo:
+		mode |= syscall.S_IFIFO
+	}
+
+	if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+		return err
+	}
+	return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+	if hdr.Typeflag == tar.TypeLink {
+		if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
+			if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
+				return err
+			}
+		}
+	} else if hdr.Typeflag != tar.TypeSymlink {
+		if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 12 - 2
pkg/archive/archive_windows.go

@@ -14,11 +14,11 @@ import (
 // path.
 func CanonicalTarNameForPath(p string) (string, error) {
 	// windows: convert windows style relative path with backslashes
-	// into forward slashes. since windows does not allow '/' or '\'
+	// into forward slashes. Since windows does not allow '/' or '\'
 	// in file names, it is mostly safe to replace however we must
 	// check just in case
 	if strings.Contains(p, "/") {
-		return "", fmt.Errorf("windows path contains forward slash: %s", p)
+		return "", fmt.Errorf("Windows path contains forward slash: %s", p)
 	}
 	return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
 
@@ -38,3 +38,13 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
 	// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
 	return
 }
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+	return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+	return nil
+}

+ 1 - 11
pkg/archive/changes.go

@@ -174,10 +174,6 @@ func (info *FileInfo) path() string {
 	return filepath.Join(info.parent.path(), info.name)
 }
 
-func (info *FileInfo) isDir() bool {
-	return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
-}
-
 func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
 
 	sizeAtEntry := len(*changes)
@@ -214,13 +210,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
 			// be visible when actually comparing the stat fields. The only time this
 			// breaks down is if some code intentionally hides a change by setting
 			// back mtime
-			if oldStat.Mode() != newStat.Mode() ||
-				oldStat.Uid() != newStat.Uid() ||
-				oldStat.Gid() != newStat.Gid() ||
-				oldStat.Rdev() != newStat.Rdev() ||
-				// Don't look at size for dirs, its not a good measure of change
-				(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
-					(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
+			if statDifferent(oldStat, newStat) ||
 				bytes.Compare(oldChild.capability, newChild.capability) != 0 {
 				change := Change{
 					Path: newChild.path(),

+ 27 - 0
pkg/archive/changes_unix.go

@@ -0,0 +1,27 @@
+// +build !windows
+
+package archive
+
+import (
+	"syscall"
+
+	"github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+	// Don't look at size for dirs, its not a good measure of change
+	if oldStat.Mode() != newStat.Mode() ||
+		oldStat.Uid() != newStat.Uid() ||
+		oldStat.Gid() != newStat.Gid() ||
+		oldStat.Rdev() != newStat.Rdev() ||
+		// Don't look at size for dirs, its not a good measure of change
+		(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
+			(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
+		return true
+	}
+	return false
+}
+
+func (info *FileInfo) isDir() bool {
+	return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
+}

+ 20 - 0
pkg/archive/changes_windows.go

@@ -0,0 +1,20 @@
+package archive
+
+import (
+	"github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+
+	// Don't look at size for dirs, its not a good measure of change
+	if oldStat.ModTime() != newStat.ModTime() ||
+		oldStat.Mode() != newStat.Mode() ||
+		oldStat.Size() != newStat.Size() && !oldStat.IsDir() {
+		return true
+	}
+	return false
+}
+
+func (info *FileInfo) isDir() bool {
+	return info.parent == nil || info.stat.IsDir()
+}

+ 1 - 1
pkg/archive/diff.go

@@ -47,7 +47,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
 			parent := filepath.Dir(hdr.Name)
 			parentPath := filepath.Join(dest, parent)
 			if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
-				err = os.MkdirAll(parentPath, 0600)
+				err = system.MkdirAll(parentPath, 0600)
 				if err != nil {
 					return 0, err
 				}

+ 23 - 2
pkg/system/lstat_windows.go

@@ -2,7 +2,28 @@
 
 package system
 
+import (
+	"os"
+)
+
+// Some explanation for my own sanity, and hopefully maintainers in the
+// future.
+//
+// Lstat calls os.Lstat to get a fileinfo interface back.
+// This is then copied into our own locally defined structure.
+// Note the Linux version uses fromStatT to do the copy back,
+// but that not strictly necessary when already in an OS specific module.
+
 func Lstat(path string) (*Stat_t, error) {
-	// should not be called on cli code path
-	return nil, ErrNotSupportedPlatform
+	fi, err := os.Lstat(path)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Stat_t{
+		name:    fi.Name(),
+		size:    fi.Size(),
+		mode:    fi.Mode(),
+		modTime: fi.ModTime(),
+		isDir:   fi.IsDir()}, nil
 }

+ 1 - 2
pkg/system/mknod_windows.go

@@ -3,10 +3,9 @@
 package system
 
 func Mknod(path string, mode uint32, dev int) error {
-	// should not be called on cli code path
 	return ErrNotSupportedPlatform
 }
 
 func Mkdev(major int64, minor int64) uint32 {
-	panic("Mkdev not implemented on windows, should not be called on cli code")
+	panic("Mkdev not implemented on Windows.")
 }

+ 2 - 0
pkg/system/stat.go

@@ -1,3 +1,5 @@
+// +build !windows
+
 package system
 
 import (

+ 26 - 7
pkg/system/stat_windows.go

@@ -3,15 +3,34 @@
 package system
 
 import (
-	"errors"
-	"syscall"
+	"os"
+	"time"
 )
 
-func fromStatT(s *syscall.Win32FileAttributeData) (*Stat_t, error) {
-	return nil, errors.New("fromStatT should not be called on windows path")
+type Stat_t struct {
+	name    string
+	size    int64
+	mode    os.FileMode
+	modTime time.Time
+	isDir   bool
 }
 
-func Stat(path string) (*Stat_t, error) {
-	// should not be called on cli code path
-	return nil, ErrNotSupportedPlatform
+func (s Stat_t) Name() string {
+	return s.name
+}
+
+func (s Stat_t) Size() int64 {
+	return s.size
+}
+
+func (s Stat_t) Mode() os.FileMode {
+	return s.mode
+}
+
+func (s Stat_t) ModTime() time.Time {
+	return s.modTime
+}
+
+func (s Stat_t) IsDir() bool {
+	return s.isDir
 }