Windows: Archive package changes for Windows daemon

Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
John Howard 2015-05-07 15:14:11 -07:00
parent 24fd826fc0
commit f883e81d79
11 changed files with 166 additions and 56 deletions

View file

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

View file

@ -7,6 +7,8 @@ import (
"errors" "errors"
"os" "os"
"syscall" "syscall"
"github.com/docker/docker/pkg/system"
) )
// canonicalTarNameForPath returns platform-specific filepath // canonicalTarNameForPath returns platform-specific filepath
@ -51,3 +53,37 @@ func major(device uint64) uint64 {
func minor(device uint64) uint64 { func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00) 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
}

View file

@ -14,11 +14,11 @@ import (
// path. // path.
func CanonicalTarNameForPath(p string) (string, error) { func CanonicalTarNameForPath(p string) (string, error) {
// windows: convert windows style relative path with backslashes // 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 // in file names, it is mostly safe to replace however we must
// check just in case // check just in case
if strings.Contains(p, "/") { 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 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 // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
return 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
}

View file

@ -174,10 +174,6 @@ func (info *FileInfo) path() string {
return filepath.Join(info.parent.path(), info.name) 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) { func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
sizeAtEntry := len(*changes) 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 // be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting // breaks down is if some code intentionally hides a change by setting
// back mtime // back mtime
if oldStat.Mode() != newStat.Mode() || if statDifferent(oldStat, newStat) ||
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()))) ||
bytes.Compare(oldChild.capability, newChild.capability) != 0 { bytes.Compare(oldChild.capability, newChild.capability) != 0 {
change := Change{ change := Change{
Path: newChild.path(), Path: newChild.path(),

27
archive/changes_unix.go Normal file
View file

@ -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
}

View file

@ -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()
}

View file

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

View file

@ -2,7 +2,28 @@
package system 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) { func Lstat(path string) (*Stat_t, error) {
// should not be called on cli code path fi, err := os.Lstat(path)
return nil, ErrNotSupportedPlatform 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
} }

View file

@ -3,10 +3,9 @@
package system package system
func Mknod(path string, mode uint32, dev int) error { func Mknod(path string, mode uint32, dev int) error {
// should not be called on cli code path
return ErrNotSupportedPlatform return ErrNotSupportedPlatform
} }
func Mkdev(major int64, minor int64) uint32 { 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.")
} }

View file

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

View file

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