diff --git a/archive/archive.go b/archive/archive.go index 76d52c0..cde4de5 100644 --- a/archive/archive.go +++ b/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 } diff --git a/archive/archive_unix.go b/archive/archive_unix.go index 6dc96a4..8a15cfe 100644 --- a/archive/archive_unix.go +++ b/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 +} diff --git a/archive/archive_windows.go b/archive/archive_windows.go index 593346d..10db4bd 100644 --- a/archive/archive_windows.go +++ b/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 +} diff --git a/archive/changes.go b/archive/changes.go index d03af0f..affafad 100644 --- a/archive/changes.go +++ b/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(), diff --git a/archive/changes_unix.go b/archive/changes_unix.go new file mode 100644 index 0000000..d780f16 --- /dev/null +++ b/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 +} diff --git a/archive/changes_windows.go b/archive/changes_windows.go new file mode 100644 index 0000000..4809b7a --- /dev/null +++ b/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() +} diff --git a/archive/diff.go b/archive/diff.go index a8314bc..fd49460 100644 --- a/archive/diff.go +++ b/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 } diff --git a/system/lstat_windows.go b/system/lstat_windows.go index 801e756..eee1be2 100644 --- a/system/lstat_windows.go +++ b/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 } diff --git a/system/mknod_windows.go b/system/mknod_windows.go index b4020c1..1811542 100644 --- a/system/mknod_windows.go +++ b/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.") } diff --git a/system/stat.go b/system/stat.go index ba22b4d..e2ecfe5 100644 --- a/system/stat.go +++ b/system/stat.go @@ -1,3 +1,5 @@ +// +build !windows + package system import ( diff --git a/system/stat_windows.go b/system/stat_windows.go index 42d29d6..b1fd39e 100644 --- a/system/stat_windows.go +++ b/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 }