pkg/archive contains code both invoked from cli (cross platform) and
daemon (linux only) and Unix-specific dependencies break compilation on
Windows. We extracted those stat-related funcs into platform specific
implementations at pkg/system and added unit tests.
Signed-off-by: Ahmet Alp Balkan <ahmetb@microsoft.com>
| ... | ... |
@@ -192,20 +192,11 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
| 192 | 192 |
|
| 193 | 193 |
hdr.Name = name |
| 194 | 194 |
|
| 195 |
- var ( |
|
| 196 |
- nlink uint32 |
|
| 197 |
- inode uint64 |
|
| 198 |
- ) |
|
| 199 |
- if stat, ok := fi.Sys().(*syscall.Stat_t); ok {
|
|
| 200 |
- nlink = uint32(stat.Nlink) |
|
| 201 |
- inode = uint64(stat.Ino) |
|
| 202 |
- // Currently go does not fill in the major/minors |
|
| 203 |
- if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || |
|
| 204 |
- stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
|
|
| 205 |
- hdr.Devmajor = int64(major(uint64(stat.Rdev))) |
|
| 206 |
- hdr.Devminor = int64(minor(uint64(stat.Rdev))) |
|
| 207 |
- } |
|
| 195 |
+ nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) |
|
| 196 |
+ if err != nil {
|
|
| 197 |
+ return err |
|
| 208 | 198 |
} |
| 199 |
+ |
|
| 209 | 200 |
// if it's a regular file and has more than 1 link, |
| 210 | 201 |
// it's hardlinked, so set the type flag accordingly |
| 211 | 202 |
if fi.Mode().IsRegular() && nlink > 1 {
|
| ... | ... |
@@ -291,7 +282,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L |
| 291 | 291 |
mode |= syscall.S_IFIFO |
| 292 | 292 |
} |
| 293 | 293 |
|
| 294 |
- if err := syscall.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
|
| 294 |
+ if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
|
| 295 | 295 |
return err |
| 296 | 296 |
} |
| 297 | 297 |
|
| 298 | 298 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+// +build !windows |
|
| 1 |
+ |
|
| 2 |
+package archive |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "errors" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
|
|
| 12 |
+ s, ok := stat.(*syscall.Stat_t) |
|
| 13 |
+ |
|
| 14 |
+ if !ok {
|
|
| 15 |
+ err = errors.New("cannot convert stat value to syscall.Stat_t")
|
|
| 16 |
+ return |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ nlink = uint32(s.Nlink) |
|
| 20 |
+ inode = uint64(s.Ino) |
|
| 21 |
+ |
|
| 22 |
+ // Currently go does not fil in the major/minors |
|
| 23 |
+ if s.Mode&syscall.S_IFBLK == syscall.S_IFBLK || |
|
| 24 |
+ s.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
|
|
| 25 |
+ hdr.Devmajor = int64(major(uint64(s.Rdev))) |
|
| 26 |
+ hdr.Devminor = int64(minor(uint64(s.Rdev))) |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ return |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func major(device uint64) uint64 {
|
|
| 33 |
+ return (device >> 8) & 0xfff |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func minor(device uint64) uint64 {
|
|
| 37 |
+ return (device & 0xff) | ((device >> 12) & 0xfff00) |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package archive |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
|
|
| 9 |
+ // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows |
|
| 10 |
+ return |
|
| 11 |
+} |
| ... | ... |
@@ -135,7 +135,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
|
| 135 | 135 |
type FileInfo struct {
|
| 136 | 136 |
parent *FileInfo |
| 137 | 137 |
name string |
| 138 |
- stat syscall.Stat_t |
|
| 138 |
+ stat *system.Stat |
|
| 139 | 139 |
children map[string]*FileInfo |
| 140 | 140 |
capability []byte |
| 141 | 141 |
added bool |
| ... | ... |
@@ -168,7 +168,7 @@ func (info *FileInfo) path() string {
|
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 | 170 |
func (info *FileInfo) isDir() bool {
|
| 171 |
- return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR |
|
| 171 |
+ return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 | 174 |
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
| ... | ... |
@@ -199,21 +199,21 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
| 199 | 199 |
oldChild, _ := oldChildren[name] |
| 200 | 200 |
if oldChild != nil {
|
| 201 | 201 |
// change? |
| 202 |
- oldStat := &oldChild.stat |
|
| 203 |
- newStat := &newChild.stat |
|
| 202 |
+ oldStat := oldChild.stat |
|
| 203 |
+ newStat := newChild.stat |
|
| 204 | 204 |
// Note: We can't compare inode or ctime or blocksize here, because these change |
| 205 | 205 |
// when copying a file into a container. However, that is not generally a problem |
| 206 | 206 |
// because any content change will change mtime, and any status change should |
| 207 | 207 |
// be visible when actually comparing the stat fields. The only time this |
| 208 | 208 |
// breaks down is if some code intentionally hides a change by setting |
| 209 | 209 |
// back mtime |
| 210 |
- if oldStat.Mode != newStat.Mode || |
|
| 211 |
- oldStat.Uid != newStat.Uid || |
|
| 212 |
- oldStat.Gid != newStat.Gid || |
|
| 213 |
- oldStat.Rdev != newStat.Rdev || |
|
| 210 |
+ if oldStat.Mode() != newStat.Mode() || |
|
| 211 |
+ oldStat.Uid() != newStat.Uid() || |
|
| 212 |
+ oldStat.Gid() != newStat.Gid() || |
|
| 213 |
+ oldStat.Rdev() != newStat.Rdev() || |
|
| 214 | 214 |
// Don't look at size for dirs, its not a good measure of change |
| 215 |
- (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || |
|
| 216 |
- !sameFsTimeSpec(system.GetLastModification(oldStat), system.GetLastModification(newStat)) || |
|
| 215 |
+ (oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) || |
|
| 216 |
+ !sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || |
|
| 217 | 217 |
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
| 218 | 218 |
change := Change{
|
| 219 | 219 |
Path: newChild.path(), |
| ... | ... |
@@ -269,14 +269,6 @@ func newRootFileInfo() *FileInfo {
|
| 269 | 269 |
return root |
| 270 | 270 |
} |
| 271 | 271 |
|
| 272 |
-func lstat(path string) (*stat, error) {
|
|
| 273 |
- s, err := system.Lstat(path) |
|
| 274 |
- if err != nil {
|
|
| 275 |
- return nil, err |
|
| 276 |
- } |
|
| 277 |
- return fromStatT(s), nil |
|
| 278 |
-} |
|
| 279 |
- |
|
| 280 | 272 |
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
| 281 | 273 |
root := newRootFileInfo() |
| 282 | 274 |
|
| ... | ... |
@@ -307,7 +299,7 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
| 307 | 307 |
parent: parent, |
| 308 | 308 |
} |
| 309 | 309 |
|
| 310 |
- s, err := lstat(path) |
|
| 310 |
+ s, err := system.Lstat(path) |
|
| 311 | 311 |
if err != nil {
|
| 312 | 312 |
return err |
| 313 | 313 |
} |
| ... | ... |
@@ -369,14 +361,6 @@ func ChangesSize(newDir string, changes []Change) int64 {
|
| 369 | 369 |
return size |
| 370 | 370 |
} |
| 371 | 371 |
|
| 372 |
-func major(device uint64) uint64 {
|
|
| 373 |
- return (device >> 8) & 0xfff |
|
| 374 |
-} |
|
| 375 |
- |
|
| 376 |
-func minor(device uint64) uint64 {
|
|
| 377 |
- return (device & 0xff) | ((device >> 12) & 0xfff00) |
|
| 378 |
-} |
|
| 379 |
- |
|
| 380 | 372 |
// ExportChanges produces an Archive from the provided changes, relative to dir. |
| 381 | 373 |
func ExportChanges(dir string, changes []Change) (Archive, error) {
|
| 382 | 374 |
reader, writer := io.Pipe() |
| ... | ... |
@@ -12,16 +12,21 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
| 13 | 13 |
|
| 14 | 14 |
"github.com/docker/docker/pkg/pools" |
| 15 |
+ "github.com/docker/docker/pkg/system" |
|
| 15 | 16 |
) |
| 16 | 17 |
|
| 17 | 18 |
// ApplyLayer parses a diff in the standard layer format from `layer`, and |
| 18 | 19 |
// applies it to the directory `dest`. |
| 19 | 20 |
func ApplyLayer(dest string, layer ArchiveReader) error {
|
| 20 | 21 |
// We need to be able to set any perms |
| 21 |
- oldmask := syscall.Umask(0) |
|
| 22 |
- defer syscall.Umask(oldmask) |
|
| 22 |
+ oldmask, err := system.Umask(0) |
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ return err |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform |
|
| 23 | 28 |
|
| 24 |
- layer, err := DecompressStream(layer) |
|
| 29 |
+ layer, err = DecompressStream(layer) |
|
| 25 | 30 |
if err != nil {
|
| 26 | 31 |
return err |
| 27 | 32 |
} |
| ... | ... |
@@ -6,11 +6,11 @@ import ( |
| 6 | 6 |
"syscall" |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 |
-func Lstat(path string) (*syscall.Stat_t, error) {
|
|
| 9 |
+func Lstat(path string) (*Stat, error) {
|
|
| 10 | 10 |
s := &syscall.Stat_t{}
|
| 11 | 11 |
err := syscall.Lstat(path, s) |
| 12 | 12 |
if err != nil {
|
| 13 | 13 |
return nil, err |
| 14 | 14 |
} |
| 15 |
- return s, nil |
|
| 15 |
+ return fromStatT(s) |
|
| 16 | 16 |
} |
| 17 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+package system |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestLstat(t *testing.T) {
|
|
| 7 |
+ file, invalid, _ := prepareFiles(t) |
|
| 8 |
+ |
|
| 9 |
+ statFile, err := Lstat(file) |
|
| 10 |
+ if err != nil {
|
|
| 11 |
+ t.Fatal(err) |
|
| 12 |
+ } |
|
| 13 |
+ if statFile == nil {
|
|
| 14 |
+ t.Fatal("returned empty stat for existing file")
|
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ statInvalid, err := Lstat(invalid) |
|
| 18 |
+ if err == nil {
|
|
| 19 |
+ t.Fatal("did not return error for non-existing file")
|
|
| 20 |
+ } |
|
| 21 |
+ if statInvalid != nil {
|
|
| 22 |
+ t.Fatal("returned non-nil stat for non-existing file")
|
|
| 23 |
+ } |
|
| 24 |
+} |
| 13 | 9 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,42 @@ |
| 0 |
+package system |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "syscall" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+type Stat struct {
|
|
| 7 |
+ mode uint32 |
|
| 8 |
+ uid uint32 |
|
| 9 |
+ gid uint32 |
|
| 10 |
+ rdev uint64 |
|
| 11 |
+ size int64 |
|
| 12 |
+ mtim syscall.Timespec |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func (s Stat) Mode() uint32 {
|
|
| 16 |
+ return s.mode |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func (s Stat) Uid() uint32 {
|
|
| 20 |
+ return s.uid |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func (s Stat) Gid() uint32 {
|
|
| 24 |
+ return s.gid |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func (s Stat) Rdev() uint64 {
|
|
| 28 |
+ return s.rdev |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func (s Stat) Size() int64 {
|
|
| 32 |
+ return s.size |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func (s Stat) Mtim() syscall.Timespec {
|
|
| 36 |
+ return s.mtim |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (s Stat) GetLastModification() syscall.Timespec {
|
|
| 40 |
+ return s.Mtim() |
|
| 41 |
+} |
| ... | ... |
@@ -4,10 +4,11 @@ import ( |
| 4 | 4 |
"syscall" |
| 5 | 5 |
) |
| 6 | 6 |
|
| 7 |
-func GetLastAccess(stat *syscall.Stat_t) syscall.Timespec {
|
|
| 8 |
- return stat.Atim |
|
| 9 |
-} |
|
| 10 |
- |
|
| 11 |
-func GetLastModification(stat *syscall.Stat_t) syscall.Timespec {
|
|
| 12 |
- return stat.Mtim |
|
| 7 |
+func fromStatT(s *syscall.Stat_t) (*Stat, error) {
|
|
| 8 |
+ return &Stat{size: s.Size,
|
|
| 9 |
+ mode: s.Mode, |
|
| 10 |
+ uid: s.Uid, |
|
| 11 |
+ gid: s.Gid, |
|
| 12 |
+ rdev: s.Rdev, |
|
| 13 |
+ mtim: s.Mtim}, nil |
|
| 13 | 14 |
} |
| 14 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package system |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "syscall" |
|
| 4 |
+ "testing" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+func TestFromStatT(t *testing.T) {
|
|
| 8 |
+ file, _, _ := prepareFiles(t) |
|
| 9 |
+ |
|
| 10 |
+ stat := &syscall.Stat_t{}
|
|
| 11 |
+ err := syscall.Lstat(file, stat) |
|
| 12 |
+ |
|
| 13 |
+ s, err := fromStatT(stat) |
|
| 14 |
+ if err != nil {
|
|
| 15 |
+ t.Fatal(err) |
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ if stat.Mode != s.Mode() {
|
|
| 19 |
+ t.Fatal("got invalid mode")
|
|
| 20 |
+ } |
|
| 21 |
+ if stat.Uid != s.Uid() {
|
|
| 22 |
+ t.Fatal("got invalid uid")
|
|
| 23 |
+ } |
|
| 24 |
+ if stat.Gid != s.Gid() {
|
|
| 25 |
+ t.Fatal("got invalid gid")
|
|
| 26 |
+ } |
|
| 27 |
+ if stat.Rdev != s.Rdev() {
|
|
| 28 |
+ t.Fatal("got invalid rdev")
|
|
| 29 |
+ } |
|
| 30 |
+ if stat.Mtim != s.Mtim() {
|
|
| 31 |
+ t.Fatal("got invalid mtim")
|
|
| 32 |
+ } |
|
| 33 |
+} |
| ... | ... |
@@ -1,13 +1,16 @@ |
| 1 |
-// +build !linux |
|
| 1 |
+// +build !linux,!windows |
|
| 2 | 2 |
|
| 3 | 3 |
package system |
| 4 | 4 |
|
| 5 |
-import "syscall" |
|
| 5 |
+import ( |
|
| 6 |
+ "syscall" |
|
| 7 |
+) |
|
| 6 | 8 |
|
| 7 |
-func GetLastAccess(stat *syscall.Stat_t) syscall.Timespec {
|
|
| 8 |
- return stat.Atimespec |
|
| 9 |
-} |
|
| 10 |
- |
|
| 11 |
-func GetLastModification(stat *syscall.Stat_t) syscall.Timespec {
|
|
| 12 |
- return stat.Mtimespec |
|
| 9 |
+func fromStatT(s *syscall.Stat_t) (*Stat, error) {
|
|
| 10 |
+ return &Stat{size: s.Size,
|
|
| 11 |
+ mode: uint32(s.Mode), |
|
| 12 |
+ uid: s.Uid, |
|
| 13 |
+ gid: s.Gid, |
|
| 14 |
+ rdev: uint64(s.Rdev), |
|
| 15 |
+ mtim: s.Mtimespec}, nil |
|
| 13 | 16 |
} |
| 14 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,12 @@ |
| 0 |
+// +build windows |
|
| 1 |
+ |
|
| 2 |
+package system |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "errors" |
|
| 6 |
+ "syscall" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func fromStatT(s *syscall.Win32FileAttributeData) (*Stat, error) {
|
|
| 10 |
+ return nil, errors.New("fromStatT should not be called on windows path")
|
|
| 11 |
+} |