Browse code

Refactor pkg/archive with a platform-independent stat struct

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>

Ahmet Alp Balkan authored on 2014/11/14 05:36:05
Showing 13 changed files
... ...
@@ -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
+}
... ...
@@ -2,11 +2,7 @@
2 2
 
3 3
 package system
4 4
 
5
-import (
6
-	"syscall"
7
-)
8
-
9
-func Lstat(path string) (*syscall.Win32FileAttributeData, error) {
5
+func Lstat(path string) (*Stat, error) {
10 6
 	// should not be called on cli code path
11 7
 	return nil, ErrNotSupportedPlatform
12 8
 }
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
+}