From cfbb6cd6256c994e9fe0d246097c9121d7b0e9bd Mon Sep 17 00:00:00 2001 From: Shawn Landden Date: Tue, 3 Dec 2013 11:03:03 -0800 Subject: [PATCH 001/126] hack/PACKAGERS.md: libdevmapper Upstream-commit: f379f667a22fd2916461654297169f70e3b39cf7 Component: engine --- components/engine/hack/PACKAGERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/hack/PACKAGERS.md b/components/engine/hack/PACKAGERS.md index 90f99a799f..1fc569056a 100644 --- a/components/engine/hack/PACKAGERS.md +++ b/components/engine/hack/PACKAGERS.md @@ -38,6 +38,7 @@ To build docker, you will need the following system dependencies * A recent version of git and mercurial * Go version 1.2 or later (see notes below regarding using Go 1.1.2 and dynbinary) * SQLite version 3.7.9 or later +* libdevmapper from lvm2 version 1.02.77 or later (http://www.sourceware.org/lvm2/) * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) under the path *src/github.com/dotcloud/docker*. From 83a69e139c589381b71a184b1647a26733e7930c Mon Sep 17 00:00:00 2001 From: Andy Chambers Date: Fri, 13 Dec 2013 00:09:05 -0500 Subject: [PATCH 002/126] Fix typos in Vagrantfile Upstream-commit: 2e6dbe87ad6e9ba87cacf818e44dea6b58be05c5 Component: engine --- components/engine/Vagrantfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/Vagrantfile b/components/engine/Vagrantfile index 6bbea51d46..84a7874f89 100644 --- a/components/engine/Vagrantfile +++ b/components/engine/Vagrantfile @@ -26,7 +26,7 @@ fi # Adding an apt gpg key is idempotent. wget -q -O - https://get.docker.io/gpg | apt-key add - -# Creating the docker.list file is idempotent, but it may overrite desired +# Creating the docker.list file is idempotent, but it may overwrite desired # settings if it already exists. This could be solved with md5sum but it # doesn't seem worth it. echo 'deb http://get.docker.io/ubuntu docker main' > \ @@ -41,7 +41,7 @@ apt-get install -q -y lxc-docker usermod -a -G docker "$user" tmp=`mktemp -q` && { - # Only install the backport kernel, don't bother upgrade if the backport is + # Only install the backport kernel, don't bother upgrading if the backport is # already installed. We want parse the output of apt so we need to save it # with 'tee'. NOTE: The installation of the kernel will trigger dkms to # install vboxguest if needed. From e5d96fb523f103e33151c5bd4e3f4ef31c2fae56 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Dec 2013 15:43:50 +0100 Subject: [PATCH 003/126] archive: Implement ApplyLayer directly Rather than calling out to tar we use the golang tar parser to directly extract the tar files. This has two major advantages: 1) We're able to replace an existing directory with a file in the new layer. This currently breaks with the external tar, since it refuses to recursively remove the destination directory in this case, and there are no options to make it do that. 2) We avoid extracting the whiteout files just to later remove them. Upstream-commit: 818c249bae8d29842834bf765299c86c09e6913e Component: engine --- components/engine/archive/diff.go | 215 +++++++++++++++-------- components/engine/archive/stat_darwin.go | 4 + components/engine/archive/stat_linux.go | 23 ++- 3 files changed, 171 insertions(+), 71 deletions(-) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index f44991ecb5..18740fe571 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -1,6 +1,9 @@ package archive import ( + "archive/tar" + "github.com/dotcloud/docker/utils" + "io" "os" "path/filepath" "strings" @@ -8,87 +11,159 @@ import ( "time" ) +// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes +// The lower 8 bit is the lower 8 bit in the minor, the following 12 bits are the major, +// and then there is the top 12 bits of then minor +func mkdev(major int64, minor int64) uint32 { + return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) +} +func timeToTimespec(time time.Time) (ts syscall.Timespec) { + if time.IsZero() { + // Return UTIME_OMIT special value + ts.Sec = 0 + ts.Nsec = ((1 << 30) - 2) + return + } + return syscall.NsecToTimespec(time.UnixNano()) +} + // ApplyLayer parses a diff in the standard layer format from `layer`, and // applies it to the directory `dest`. func ApplyLayer(dest string, layer Archive) error { - // Poor man's diff applyer in 2 steps: + // We need to be able to set any perms + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) - // Step 1: untar everything in place - if err := Untar(layer, dest, nil); err != nil { - return err - } + tr := tar.NewReader(layer) - modifiedDirs := make(map[string]*syscall.Stat_t) - addDir := func(file string) { - d := filepath.Dir(file) - if _, exists := modifiedDirs[d]; !exists { - if s, err := os.Lstat(d); err == nil { - if sys := s.Sys(); sys != nil { - if stat, ok := sys.(*syscall.Stat_t); ok { - modifiedDirs[d] = stat + var dirs []*tar.Header + + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + // end of tar archive + break + } + if err != nil { + return err + } + + // Skip AUFS metadata dirs + if strings.HasPrefix(hdr.Name, ".wh..wh.") { + continue + } + + path := filepath.Join(dest, hdr.Name) + base := filepath.Base(path) + if strings.HasPrefix(base, ".wh.") { + originalBase := base[len(".wh."):] + originalPath := filepath.Join(filepath.Dir(path), originalBase) + if err := os.RemoveAll(originalPath); err != nil { + return err + } + } else { + // If path exits we almost always just want to remove and replace it + // The only exception is when it is a directory *and* the file from + // the layer is also a directory. Then we want to merge them (i.e. + // just apply the metadata from the layer). + hasDir := false + if fi, err := os.Lstat(path); err == nil { + if fi.IsDir() && hdr.Typeflag == tar.TypeDir { + hasDir = true + } else { + if err := os.RemoveAll(path); err != nil { + return err + } + } + } + + switch hdr.Typeflag { + case tar.TypeDir: + if !hasDir { + err = os.Mkdir(path, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + } + dirs = append(dirs, hdr) + + case tar.TypeReg, tar.TypeRegA: + // Source is regular file + file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + if _, err := io.Copy(file, tr); err != nil { + file.Close() + return err + } + 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 := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil { + return err + } + + case tar.TypeLink: + if err := os.Link(filepath.Join(dest, hdr.Linkname), path); err != nil { + return err + } + + case tar.TypeSymlink: + if err := os.Symlink(hdr.Linkname, path); err != nil { + return err + } + + default: + utils.Debugf("unhandled type %d\n", hdr.Typeflag) + } + + if err = syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + return err + } + + // 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.TypeSymlink { + err = syscall.Chmod(path, uint32(hdr.Mode&07777)) + if err != nil { + return err + } + } + + // Directories must be handled at the end to avoid further + // file creation in them to modify the mtime + if hdr.Typeflag != tar.TypeDir { + ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} + // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and + if hdr.Typeflag != tar.TypeSymlink { + if err := syscall.UtimesNano(path, ts); err != nil { + return err + } + } else { + if err := LUtimesNano(path, ts); err != nil { + return err } } } } } - // Step 2: walk for whiteouts and apply them, removing them in the process - err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error { - if err != nil { - if os.IsNotExist(err) { - // This happens in the case of whiteouts in parent dir removing a directory - // We just ignore it - return filepath.SkipDir - } - return err - } - - // Rebase path - path, err := filepath.Rel(dest, fullPath) - if err != nil { - return err - } - path = filepath.Join("/", path) - - // Skip AUFS metadata - if matched, err := filepath.Match("/.wh..wh.*", path); err != nil { - return err - } else if matched { - addDir(fullPath) - if err := os.RemoveAll(fullPath); err != nil { - return err - } - } - - filename := filepath.Base(path) - if strings.HasPrefix(filename, ".wh.") { - rmTargetName := filename[len(".wh."):] - rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName) - - // Remove the file targeted by the whiteout - addDir(rmTargetPath) - if err := os.RemoveAll(rmTargetPath); err != nil { - return err - } - // Remove the whiteout itself - addDir(fullPath) - if err := os.RemoveAll(fullPath); err != nil { - return err - } - } - return nil - }) - if err != nil { - return err - } - - for k, v := range modifiedDirs { - lastAccess := getLastAccess(v) - lastModification := getLastModification(v) - aTime := time.Unix(lastAccess.Unix()) - mTime := time.Unix(lastModification.Unix()) - - if err := os.Chtimes(k, aTime, mTime); err != nil { + for _, hdr := range dirs { + path := filepath.Join(dest, hdr.Name) + ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} + if err := syscall.UtimesNano(path, ts); err != nil { return err } } diff --git a/components/engine/archive/stat_darwin.go b/components/engine/archive/stat_darwin.go index 53ae9dee2f..e041783ec6 100644 --- a/components/engine/archive/stat_darwin.go +++ b/components/engine/archive/stat_darwin.go @@ -9,3 +9,7 @@ func getLastAccess(stat *syscall.Stat_t) syscall.Timespec { func getLastModification(stat *syscall.Stat_t) syscall.Timespec { return stat.Mtimespec } + +func LUtimesNano(path string, ts []syscall.Timespec) error { + return nil +} diff --git a/components/engine/archive/stat_linux.go b/components/engine/archive/stat_linux.go index 50b4627c4a..2203a46aff 100644 --- a/components/engine/archive/stat_linux.go +++ b/components/engine/archive/stat_linux.go @@ -1,6 +1,9 @@ package archive -import "syscall" +import ( + "syscall" + "unsafe" +) func getLastAccess(stat *syscall.Stat_t) syscall.Timespec { return stat.Atim @@ -9,3 +12,21 @@ func getLastAccess(stat *syscall.Stat_t) syscall.Timespec { func getLastModification(stat *syscall.Stat_t) syscall.Timespec { return stat.Mtim } + +func LUtimesNano(path string, ts []syscall.Timespec) error { + // These are not currently availible in syscall + AT_FDCWD := -100 + AT_SYMLINK_NOFOLLOW := 0x100 + + var _path *byte + _path, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS { + return err + } + + return nil +} From 51b58640b01f1bf1d226b4132e17dcca9485327a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Dec 2013 15:46:41 +0100 Subject: [PATCH 004/126] Fix change detection when applying tar layers The default gnu tar format has no sub-second precision mtime support, and the golang tar writer currently doesn't support that either. This means if we export the changes from a container we will not get zeron in the sub-second precision field when the change is applied. This means we can't compare that to the original without getting a spurious change. So, we detect this case by treating a case where the seconds match and either of the two nanoseconds are zero as equal. Upstream-commit: 10cd902f900392a2f10a6f8763bba70607ea0d41 Component: engine --- components/engine/archive/changes.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index a4076fc0ad..8fe9ff2233 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" "syscall" + "time" ) type ChangeType int @@ -34,6 +35,21 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } +// Gnu tar and the go tar writer don't have sub-second mtime +// precision, which is problematic when we apply changes via tar +// files, we handle this by comparing for exact times, *or* same +// second count and either a or b having exactly 0 nanoseconds +func sameFsTime(a, b time.Time) bool { + return a == b || + (a.Unix() == b.Unix() && + (a.Nanosecond() == 0 || b.Nanosecond() == 0)) +} + +func sameFsTimeSpec(a, b syscall.Timespec) bool { + return a.Sec == b.Sec && + (a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0) +} + func Changes(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { @@ -85,7 +101,7 @@ func Changes(layers []string, rw string) ([]Change, error) { // However, if it's a directory, maybe it wasn't actually modified. // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { + if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) { // Both directories are the same, don't record the change return nil } @@ -181,7 +197,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || - getLastModification(oldStat) != getLastModification(newStat) { + !sameFsTimeSpec(getLastModification(oldStat), getLastModification(newStat)) { change := Change{ Path: newChild.path(), Kind: ChangeModify, From 7bf68dd3dec46a067e7b208a63dbbb7a2484ce23 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Dec 2013 15:50:25 +0100 Subject: [PATCH 005/126] Re-enable TestApplyLayer With the previous two changes we now pass this test. Upstream-commit: a8af12f80a4a1678988b4667e5211d4e576ce903 Component: engine --- components/engine/archive/changes_test.go | 72 +++++++++++------------ 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/components/engine/archive/changes_test.go b/components/engine/archive/changes_test.go index 714ab71e2d..e11ed90ac1 100644 --- a/components/engine/archive/changes_test.go +++ b/components/engine/archive/changes_test.go @@ -258,48 +258,44 @@ func TestChangesDirsMutated(t *testing.T) { } func TestApplyLayer(t *testing.T) { - t.Skip("Skipping TestApplyLayer due to known failures") // Disable this for now as it is broken - return + src, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + createSampleDir(t, src) + defer os.RemoveAll(src) + dst := src + "-copy" + if err := copyDir(src, dst); err != nil { + t.Fatal(err) + } + mutateSampleDir(t, dst) + defer os.RemoveAll(dst) - // src, err := ioutil.TempDir("", "docker-changes-test") - // if err != nil { - // t.Fatal(err) - // } - // createSampleDir(t, src) - // dst := src + "-copy" - // if err := copyDir(src, dst); err != nil { - // t.Fatal(err) - // } - // mutateSampleDir(t, dst) + changes, err := ChangesDirs(dst, src) + if err != nil { + t.Fatal(err) + } - // changes, err := ChangesDirs(dst, src) - // if err != nil { - // t.Fatal(err) - // } + layer, err := ExportChanges(dst, changes) + if err != nil { + t.Fatal(err) + } - // layer, err := ExportChanges(dst, changes) - // if err != nil { - // t.Fatal(err) - // } + layerCopy, err := NewTempArchive(layer, "") + if err != nil { + t.Fatal(err) + } - // layerCopy, err := NewTempArchive(layer, "") - // if err != nil { - // t.Fatal(err) - // } + if err := ApplyLayer(src, layerCopy); err != nil { + t.Fatal(err) + } - // if err := ApplyLayer(src, layerCopy); err != nil { - // t.Fatal(err) - // } + changes2, err := ChangesDirs(src, dst) + if err != nil { + t.Fatal(err) + } - // changes2, err := ChangesDirs(src, dst) - // if err != nil { - // t.Fatal(err) - // } - - // if len(changes2) != 0 { - // t.Fatalf("Unexpected differences after re applying mutation: %v", changes) - // } - - // os.RemoveAll(src) - // os.RemoveAll(dst) + if len(changes2) != 0 { + t.Fatalf("Unexpected differences after re applying mutation: %v", changes2) + } } From bc3c34fbfda9ef5772691db3e1710fc0be23cdb5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 15 Dec 2013 03:13:41 -0500 Subject: [PATCH 006/126] Updated Introduction link Previously the introduction link pointed to www.docker.io. That did not seem to make a lot of sense to me so instead I pointed it at: http://www.docker.io/learn_more/ Upstream-commit: 58daccab26bb9bcdbf4b14c20cc6dd0b257aa6b3 Component: engine --- components/engine/docs/sources/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/index.rst b/components/engine/docs/sources/index.rst index 88752ac3bf..1fb82f3bec 100644 --- a/components/engine/docs/sources/index.rst +++ b/components/engine/docs/sources/index.rst @@ -25,7 +25,7 @@ currently in active development, so this documentation will change frequently. For an overview of Docker, please see the `Introduction -`_. When you're ready to start working with +`_. When you're ready to start working with Docker, we have a `quick start `_ and a more in-depth guide to :ref:`ubuntu_linux` and other :ref:`installation_list` paths including prebuilt binaries, From 638d177a3b44e60dad39994dfee4411a063e606f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 16 Dec 2013 13:24:35 +1000 Subject: [PATCH 007/126] please, don't use 30% of the screen for whitespace, and thus compress the examples so they are ~80 chars wide - the output of 'docker ps' and 'docker images' becomes needlessly unreadable Upstream-commit: e1efd4cb8c6a5c645285b1f39d4508419e5626c9 Component: engine --- components/engine/docs/theme/docker/layout.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/theme/docker/layout.html b/components/engine/docs/theme/docker/layout.html index 67b3e49745..9e7fb6937d 100755 --- a/components/engine/docs/theme/docker/layout.html +++ b/components/engine/docs/theme/docker/layout.html @@ -86,13 +86,13 @@ -
+
-
+
-