diff options
Diffstat (limited to 'system')
7 files changed, 869 insertions, 5 deletions
diff --git a/system/docker/docker.SlackBuild b/system/docker/docker.SlackBuild index 46128a0377..dfc00c85f3 100644 --- a/system/docker/docker.SlackBuild +++ b/system/docker/docker.SlackBuild @@ -5,11 +5,11 @@ # Written by Vincent Batts <vbatts@hashbangbash.com> PRGNAM=docker -VERSION=${VERSION:-1.6.2} +VERSION=${VERSION:-1.8.2} BUILD=${BUILD:-1} TAG=${TAG:-_SBo} -GITHASH=${GITHASH:-7c8fca2} +GITHASH=${GITHASH:-0a8c2e3} if [ -z "$ARCH" ]; then case "$( uname -m )" in @@ -61,6 +61,12 @@ unset GOPATH # https://github.com/docker/docker/commit/6922f1be08111d889b0585b763b08f92d7a55e05 patch -p1 -R < $CWD/docker-btrfs.patch +# a couple of patches that missed the 1.8.2 release window, but are essential +for patch in ${CWD}/patches/*.patch +do + patch -p1 < ${patch} +done + mkdir -p ${PKG}/usr/share/gocode/src/github.com/docker/docker cp -a . ${PKG}/usr/share/gocode/src/github.com/docker/docker/ diff --git a/system/docker/docker.info b/system/docker/docker.info index e80706c10b..688dc88d97 100644 --- a/system/docker/docker.info +++ b/system/docker/docker.info @@ -1,10 +1,10 @@ PRGNAM="docker" -VERSION="1.6.2" +VERSION="1.8.2" HOMEPAGE="https://docker.io/" DOWNLOAD="UNSUPPORTED" MD5SUM="" -DOWNLOAD_x86_64="https://github.com/docker/docker/archive/v1.6.2.tar.gz" -MD5SUM_x86_64="81a1a015ec0520d739ec721f8295d94f" +DOWNLOAD_x86_64="https://github.com/docker/docker/archive/v1.8.2.tar.gz" +MD5SUM_x86_64="4faf25b356900f3e7599783ad4565e69" REQUIRES="google-go-lang" MAINTAINER="Vincent Batts" EMAIL="vbatts@hashbangbash.com" diff --git a/system/docker/patches/0001-devicemapper-fix-zero-sized-field-access.patch b/system/docker/patches/0001-devicemapper-fix-zero-sized-field-access.patch new file mode 100644 index 0000000000..60d95db7c9 --- /dev/null +++ b/system/docker/patches/0001-devicemapper-fix-zero-sized-field-access.patch @@ -0,0 +1,88 @@ +From f7236a195c84687edb74fec28b6c4cc98e34185c Mon Sep 17 00:00:00 2001 +From: Vincent Batts <vbatts@redhat.com> +Date: Fri, 7 Aug 2015 10:18:20 -0400 +Subject: [PATCH 1/4] devicemapper: fix zero-sized field access + +Fixes: #15279 + +Due to +https://github.com/golang/go/commit/7904946eeb35faece61bbf6f5b3cc8be2f519c17 +the devices field is dropped. + +This solution works on go1.4 and go1.5 + +Signed-off-by: Vincent Batts <vbatts@redhat.com> +--- + daemon/graphdriver/devmapper/deviceset.go | 14 +++++++++----- + pkg/devicemapper/devmapper_wrapper.go | 18 +++++++++++++++--- + 2 files changed, 24 insertions(+), 8 deletions(-) + +diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go +index 2eee330..a80736a 100644 +--- a/daemon/graphdriver/devmapper/deviceset.go ++++ b/daemon/graphdriver/devmapper/deviceset.go +@@ -1482,12 +1482,16 @@ func (devices *DeviceSet) deactivatePool() error { + if err != nil { + return err + } +- if d, err := devicemapper.GetDeps(devname); err == nil { +- // Access to more Debug output +- logrus.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d) ++ ++ if devinfo.Exists == 0 { ++ return nil + } +- if devinfo.Exists != 0 { +- return devicemapper.RemoveDevice(devname) ++ if err := devicemapper.RemoveDevice(devname); err != nil { ++ return err ++ } ++ ++ if d, err := devicemapper.GetDeps(devname); err == nil { ++ logrus.Warnf("[devmapper] device %s still has %d active dependents", devname, d.Count) + } + + return nil +diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go +index 87c2003..44ca772 100644 +--- a/pkg/devicemapper/devmapper_wrapper.go ++++ b/pkg/devicemapper/devmapper_wrapper.go +@@ -38,7 +38,10 @@ static void log_with_errno_init() + */ + import "C" + +-import "unsafe" ++import ( ++ "reflect" ++ "unsafe" ++) + + type ( + CDmTask C.struct_dm_task +@@ -184,12 +187,21 @@ func dmTaskGetDepsFct(task *CDmTask) *Deps { + if Cdeps == nil { + return nil + } ++ ++ // golang issue: https://github.com/golang/go/issues/11925 ++ hdr := reflect.SliceHeader{ ++ Data: uintptr(unsafe.Pointer(uintptr(unsafe.Pointer(Cdeps)) + unsafe.Sizeof(*Cdeps))), ++ Len: int(Cdeps.count), ++ Cap: int(Cdeps.count), ++ } ++ devices := *(*[]C.uint64_t)(unsafe.Pointer(&hdr)) ++ + deps := &Deps{ + Count: uint32(Cdeps.count), + Filler: uint32(Cdeps.filler), + } +- for _, device := range Cdeps.device { +- deps.Device = append(deps.Device, (uint64)(device)) ++ for _, device := range devices { ++ deps.Device = append(deps.Device, uint64(device)) + } + return deps + } +-- +2.4.3 + diff --git a/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch b/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch new file mode 100644 index 0000000000..f8d827fb82 --- /dev/null +++ b/system/docker/patches/0002-Avoid-buffering-to-tempfile-when-pushing-with-V2.patch @@ -0,0 +1,220 @@ +From cccc745d93a59fdbb4dd7d7562ee8dd684a00786 Mon Sep 17 00:00:00 2001 +From: Stephen J Day <stephen.day@docker.com> +Date: Tue, 11 Aug 2015 13:47:08 -0700 +Subject: [PATCH 2/4] Avoid buffering to tempfile when pushing with V2 + +The practice of buffering to a tempfile during a pushing contributes massively +to slow V2 push performance perception. The protocol was actually designed to +avoid precalculation, supporting cut-through data push. This means we can +assemble the layer, calculate its digest and push to the remote endpoint, all +at the same time. + +This should increase performance massively on systems with slow disks or IO +bottlenecks. + +Signed-off-by: Stephen J Day <stephen.day@docker.com> +--- + graph/graph.go | 21 -------------- + graph/push_v2.go | 51 ++++++++++++++------------------- + integration-cli/docker_cli_push_test.go | 2 +- + pkg/jsonmessage/jsonmessage.go | 6 ++++ + pkg/jsonmessage/jsonmessage_test.go | 4 +-- + 5 files changed, 31 insertions(+), 53 deletions(-) + +diff --git a/graph/graph.go b/graph/graph.go +index be911b0..885de87 100644 +--- a/graph/graph.go ++++ b/graph/graph.go +@@ -2,7 +2,6 @@ package graph + + import ( + "compress/gzip" +- "crypto/sha256" + "encoding/json" + "errors" + "fmt" +@@ -329,26 +328,6 @@ func (graph *Graph) newTempFile() (*os.File, error) { + return ioutil.TempFile(tmp, "") + } + +-func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) { +- var ( +- h = sha256.New() +- w = gzip.NewWriter(io.MultiWriter(f, h)) +- ) +- _, err := io.Copy(w, src) +- w.Close() +- if err != nil { +- return 0, "", err +- } +- n, err := f.Seek(0, os.SEEK_CUR) +- if err != nil { +- return 0, "", err +- } +- if _, err := f.Seek(0, 0); err != nil { +- return 0, "", err +- } +- return n, digest.NewDigest("sha256", h), nil +-} +- + // Delete atomically removes an image from the graph. + func (graph *Graph) Delete(name string) error { + id, err := graph.idIndex.Get(name) +diff --git a/graph/push_v2.go b/graph/push_v2.go +index 92d63ca..0ec8cfd 100644 +--- a/graph/push_v2.go ++++ b/graph/push_v2.go +@@ -2,8 +2,8 @@ package graph + + import ( + "fmt" ++ "io" + "io/ioutil" +- "os" + + "github.com/Sirupsen/logrus" + "github.com/docker/distribution" +@@ -199,7 +199,7 @@ func (p *v2Pusher) pushV2Tag(tag string) error { + func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) { + out := p.config.OutStream + +- out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Preparing", nil)) + + image, err := p.graph.Get(img.ID) + if err != nil { +@@ -209,52 +209,45 @@ func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (d + if err != nil { + return "", err + } +- +- tf, err := p.graph.newTempFile() +- if err != nil { +- return "", err +- } +- defer func() { +- tf.Close() +- os.Remove(tf.Name()) +- }() +- +- size, dgst, err := bufferToFile(tf, arch) +- if err != nil { +- return "", err +- } ++ defer arch.Close() + + // Send the layer +- logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) + layerUpload, err := bs.Create(context.Background()) + if err != nil { + return "", err + } + defer layerUpload.Close() + ++ digester := digest.Canonical.New() ++ tee := io.TeeReader(arch, digester.Hash()) ++ + reader := progressreader.New(progressreader.Config{ +- In: ioutil.NopCloser(tf), ++ In: ioutil.NopCloser(tee), // we'll take care of close here. + Out: out, + Formatter: p.sf, +- Size: int(size), +- NewLines: false, +- ID: stringid.TruncateID(img.ID), +- Action: "Pushing", ++ // TODO(stevvooe): This may cause a size reporting error. Try to get ++ // this from tar-split or elsewhere. The main issue here is that we ++ // don't want to buffer to disk *just* to calculate the size. ++ Size: int(img.Size), ++ ++ NewLines: false, ++ ID: stringid.TruncateID(img.ID), ++ Action: "Pushing", + }) +- n, err := layerUpload.ReadFrom(reader) ++ ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushing", nil)) ++ nn, err := io.Copy(layerUpload, reader) + if err != nil { + return "", err + } +- if n != size { +- return "", fmt.Errorf("short upload: only wrote %d of %d", n, size) +- } + +- desc := distribution.Descriptor{Digest: dgst} +- if _, err := layerUpload.Commit(context.Background(), desc); err != nil { ++ dgst := digester.Digest() ++ if _, err := layerUpload.Commit(context.Background(), distribution.Descriptor{Digest: dgst}); err != nil { + return "", err + } + +- out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil)) ++ logrus.Debugf("uploaded layer %s (%s), %d bytes", img.ID, dgst, nn) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pushed", nil)) + + return dgst, nil + } +diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go +index 111e9f3..c17a0ea 100644 +--- a/integration-cli/docker_cli_push_test.go ++++ b/integration-cli/docker_cli_push_test.go +@@ -108,7 +108,7 @@ func (s *DockerRegistrySuite) TestPushInterrupt(c *check.C) { + } + + // Interrupt push (yes, we have no idea at what point it will get killed). +- time.Sleep(200 * time.Millisecond) ++ time.Sleep(50 * time.Millisecond) // dependent on race condition. + if err := pushCmd.Process.Kill(); err != nil { + c.Fatalf("Failed to kill push process: %v", err) + } +diff --git a/pkg/jsonmessage/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go +index 7db1626..c4b311e 100644 +--- a/pkg/jsonmessage/jsonmessage.go ++++ b/pkg/jsonmessage/jsonmessage.go +@@ -61,8 +61,14 @@ func (p *JSONProgress) String() string { + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } ++ + numbersBox = fmt.Sprintf("%8v/%v", current, total) + ++ if p.Current > p.Total { ++ // remove total display if the reported current is wonky. ++ numbersBox = fmt.Sprintf("%8v", current) ++ } ++ + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := time.Now().UTC().Sub(time.Unix(int64(p.Start), 0)) + perEntry := fromStart / time.Duration(p.Current) +diff --git a/pkg/jsonmessage/jsonmessage_test.go b/pkg/jsonmessage/jsonmessage_test.go +index 2e78fa7..889b0ba 100644 +--- a/pkg/jsonmessage/jsonmessage_test.go ++++ b/pkg/jsonmessage/jsonmessage_test.go +@@ -3,12 +3,12 @@ package jsonmessage + import ( + "bytes" + "fmt" ++ "strings" + "testing" + "time" + + "github.com/docker/docker/pkg/term" + "github.com/docker/docker/pkg/timeutils" +- "strings" + ) + + func TestError(t *testing.T) { +@@ -45,7 +45,7 @@ func TestProgress(t *testing.T) { + } + + // this number can't be negative gh#7136 +- expected = "[==================================================>] 50 B/40 B" ++ expected = "[==================================================>] 50 B" + jp5 := JSONProgress{Current: 50, Total: 40} + if jp5.String() != expected { + t.Fatalf("Expected %q, got %q", expected, jp5.String()) +-- +2.4.3 + diff --git a/system/docker/patches/0003-Refactoring-of-pullV2Tag.patch b/system/docker/patches/0003-Refactoring-of-pullV2Tag.patch new file mode 100644 index 0000000000..97f44bc78f --- /dev/null +++ b/system/docker/patches/0003-Refactoring-of-pullV2Tag.patch @@ -0,0 +1,189 @@ +From e12038fb4a2f74314cf23860ea97528418832ba5 Mon Sep 17 00:00:00 2001 +From: Alexander Morozov <lk4d4@docker.com> +Date: Wed, 12 Aug 2015 20:23:56 -0700 +Subject: [PATCH 3/4] Refactoring of pullV2Tag + +* use downloadInfo pointers everywhere +* use downloads slice only for things that we really download +* cleanup tmp files in all cases + +Signed-off-by: Alexander Morozov <lk4d4@docker.com> +--- + graph/pull_v2.go | 106 ++++++++++++++++++++++++++++++------------------------- + 1 file changed, 58 insertions(+), 48 deletions(-) + +diff --git a/graph/pull_v2.go b/graph/pull_v2.go +index 1dbb9fe..ba5e8ce 100644 +--- a/graph/pull_v2.go ++++ b/graph/pull_v2.go +@@ -139,6 +139,7 @@ func (p *v2Puller) download(di *downloadInfo) { + di.err <- err + return + } ++ di.tmpFile = tmpFile + + blobs := p.repo.Blobs(context.Background()) + +@@ -187,7 +188,6 @@ func (p *v2Puller) download(di *downloadInfo) { + out.Write(p.sf.FormatProgress(stringid.TruncateID(di.img.ID), "Download complete", nil)) + + logrus.Debugf("Downloaded %s to tempfile %s", di.img.ID, tmpFile.Name()) +- di.tmpFile = tmpFile + di.layer = layerDownload + + di.err <- nil +@@ -243,9 +243,9 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) + + out.Write(p.sf.FormatStatus(tag, "Pulling from %s", p.repo.Name())) + +- downloads := make([]downloadInfo, len(manifest.FSLayers)) ++ var downloads []*downloadInfo + +- layerIDs := []string{} ++ var layerIDs []string + defer func() { + p.graph.Release(p.sessionID, layerIDs...) + }() +@@ -256,66 +256,75 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) + logrus.Debugf("error getting image v1 json: %v", err) + return false, err + } +- downloads[i].img = img +- downloads[i].digest = manifest.FSLayers[i].BlobSum +- + p.graph.Retain(p.sessionID, img.ID) + layerIDs = append(layerIDs, img.ID) + + // Check if exists + if p.graph.Exists(img.ID) { + logrus.Debugf("Image already exists: %s", img.ID) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Already exists", nil)) + continue + } +- + out.Write(p.sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil)) + +- downloads[i].err = make(chan error) +- downloads[i].out = pipeWriter +- go p.download(&downloads[i]) ++ d := &downloadInfo{ ++ img: img, ++ digest: manifest.FSLayers[i].BlobSum, ++ // TODO: seems like this chan buffer solved hanging problem in go1.5, ++ // this can indicate some deeper problem that somehow we never take ++ // error from channel in loop below ++ err: make(chan error, 1), ++ out: pipeWriter, ++ } ++ downloads = append(downloads, d) ++ ++ go p.download(d) + } + +- var tagUpdated bool +- for i := len(downloads) - 1; i >= 0; i-- { +- d := &downloads[i] +- if d.err != nil { +- if err := <-d.err; err != nil { +- return false, err +- } +- } +- if d.layer != nil { +- // if tmpFile is empty assume download and extracted elsewhere +- defer os.Remove(d.tmpFile.Name()) +- defer d.tmpFile.Close() +- d.tmpFile.Seek(0, 0) ++ // run clean for all downloads to prevent leftovers ++ for _, d := range downloads { ++ defer func(d *downloadInfo) { + if d.tmpFile != nil { +- +- reader := progressreader.New(progressreader.Config{ +- In: d.tmpFile, +- Out: out, +- Formatter: p.sf, +- Size: int(d.size), +- NewLines: false, +- ID: stringid.TruncateID(d.img.ID), +- Action: "Extracting", +- }) +- +- err = p.graph.Register(d.img, reader) +- if err != nil { +- return false, err ++ d.tmpFile.Close() ++ if err := os.RemoveAll(d.tmpFile.Name()); err != nil { ++ logrus.Errorf("Failed to remove temp file: %s", d.tmpFile.Name()) + } ++ } ++ }(d) ++ } + +- if err := p.graph.SetDigest(d.img.ID, d.digest); err != nil { +- return false, err +- } ++ var tagUpdated bool ++ for _, d := range downloads { ++ if err := <-d.err; err != nil { ++ return false, err ++ } ++ if d.layer == nil { ++ continue ++ } ++ // if tmpFile is empty assume download and extracted elsewhere ++ d.tmpFile.Seek(0, 0) ++ reader := progressreader.New(progressreader.Config{ ++ In: d.tmpFile, ++ Out: out, ++ Formatter: p.sf, ++ Size: int(d.size), ++ NewLines: false, ++ ID: stringid.TruncateID(d.img.ID), ++ Action: "Extracting", ++ }) ++ ++ err = p.graph.Register(d.img, reader) ++ if err != nil { ++ return false, err ++ } + +- // FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted) +- } +- out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil)) +- tagUpdated = true +- } else { +- out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Already exists", nil)) ++ if err := p.graph.SetDigest(d.img.ID, d.digest); err != nil { ++ return false, err + } ++ ++ // FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted) ++ out.Write(p.sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil)) ++ tagUpdated = true + } + + manifestDigest, _, err := digestFromManifest(manifest, p.repoInfo.LocalName) +@@ -342,17 +351,18 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (verified bool, err error) + out.Write(p.sf.FormatStatus(p.repo.Name()+":"+tag, "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.")) + } + ++ firstID := layerIDs[len(layerIDs)-1] + if utils.DigestReference(tag) { + // TODO(stevvooe): Ideally, we should always set the digest so we can + // use the digest whether we pull by it or not. Unfortunately, the tag + // store treats the digest as a separate tag, meaning there may be an + // untagged digest image that would seem to be dangling by a user. +- if err = p.SetDigest(p.repoInfo.LocalName, tag, downloads[0].img.ID); err != nil { ++ if err = p.SetDigest(p.repoInfo.LocalName, tag, firstID); err != nil { + return false, err + } + } else { + // only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest) +- if err = p.Tag(p.repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil { ++ if err = p.Tag(p.repoInfo.LocalName, tag, firstID, true); err != nil { + return false, err + } + } +-- +2.4.3 + diff --git a/system/docker/patches/0004-deamon-events-use-UnixNano-and-no-goroutine.patch b/system/docker/patches/0004-deamon-events-use-UnixNano-and-no-goroutine.patch new file mode 100644 index 0000000000..9b9836842a --- /dev/null +++ b/system/docker/patches/0004-deamon-events-use-UnixNano-and-no-goroutine.patch @@ -0,0 +1,72 @@ +From 67c185dea42b6d4dc8f53280446488621ab04f7c Mon Sep 17 00:00:00 2001 +From: Vincent Batts <vbatts@redhat.com> +Date: Tue, 15 Sep 2015 15:05:17 -0400 +Subject: [PATCH 4/4] deamon/events: use UnixNano and no goroutine + +Signed-off-by: Vincent Batts <vbatts@redhat.com> +--- + daemon/events/events.go | 24 +++++++++++------------- + pkg/jsonmessage/jsonmessage.go | 5 +++-- + 2 files changed, 14 insertions(+), 15 deletions(-) + +diff --git a/daemon/events/events.go b/daemon/events/events.go +index 07ee29a..aeb22e8 100644 +--- a/daemon/events/events.go ++++ b/daemon/events/events.go +@@ -45,19 +45,17 @@ func (e *Events) Evict(l chan interface{}) { + // Log broadcasts event to listeners. Each listener has 100 millisecond for + // receiving event or it will be skipped. + func (e *Events) Log(action, id, from string) { +- go func() { +- e.mu.Lock() +- jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: time.Now().UTC().Unix()} +- if len(e.events) == cap(e.events) { +- // discard oldest event +- copy(e.events, e.events[1:]) +- e.events[len(e.events)-1] = jm +- } else { +- e.events = append(e.events, jm) +- } +- e.mu.Unlock() +- e.pub.Publish(jm) +- }() ++ e.mu.Lock() ++ jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, TimeNano: time.Now().UTC().UnixNano()} ++ if len(e.events) == cap(e.events) { ++ // discard oldest event ++ copy(e.events, e.events[1:]) ++ e.events[len(e.events)-1] = jm ++ } else { ++ e.events = append(e.events, jm) ++ } ++ e.mu.Unlock() ++ e.pub.Publish(jm) + } + + // SubscribersCount returns number of event listeners +diff --git a/pkg/jsonmessage/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go +index c4b311e..8a24d9c 100644 +--- a/pkg/jsonmessage/jsonmessage.go ++++ b/pkg/jsonmessage/jsonmessage.go +@@ -90,6 +90,7 @@ type JSONMessage struct { + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + Time int64 `json:"time,omitempty"` ++ TimeNano int64 `json:"timeNano,omitempty"` + Error *JSONError `json:"errorDetail,omitempty"` + ErrorMessage string `json:"error,omitempty"` //deprecated + } +@@ -109,8 +110,8 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error { + } else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal + return nil + } +- if jm.Time != 0 { +- fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(timeutils.RFC3339NanoFixed)) ++ if jm.Time != 0 || jm.TimeNano != 0 { ++ fmt.Fprintf(out, "%s ", time.Unix(jm.Time, jm.TimeNano).Format(timeutils.RFC3339NanoFixed)) + } + if jm.ID != "" { + fmt.Fprintf(out, "%s: ", jm.ID) +-- +2.4.3 + diff --git a/system/docker/patches/0005-vendor-update-tar-split-to-v0.9.10.patch b/system/docker/patches/0005-vendor-update-tar-split-to-v0.9.10.patch new file mode 100644 index 0000000000..f343afae57 --- /dev/null +++ b/system/docker/patches/0005-vendor-update-tar-split-to-v0.9.10.patch @@ -0,0 +1,289 @@ +From d8029ceb202fda8160855c07081dc51aae1ec1ad Mon Sep 17 00:00:00 2001 +From: Vincent Batts <vbatts@redhat.com> +Date: Wed, 23 Sep 2015 15:50:23 -0400 +Subject: [PATCH 5/5] vendor: update tar-split to v0.9.10 + +This addresses handling of non-utf8 file names, namely iso-8859-1. + +https://github.com/docker/docker/issues/16516 + +Reported-by: @kwk +Signed-off-by: Vincent Batts <vbatts@redhat.com> +--- + hack/vendor.sh | 2 +- + .../vbatts/tar-split/archive/tar/common.go | 28 ++++++++++++++-- + .../vbatts/tar-split/archive/tar/reader.go | 15 ++++++++- + .../vbatts/tar-split/archive/tar/writer.go | 2 +- + .../vbatts/tar-split/tar/asm/assemble.go | 4 +-- + .../vbatts/tar-split/tar/asm/disassemble.go | 11 +++--- + .../vbatts/tar-split/tar/storage/entry.go | 39 ++++++++++++++++++++++ + .../vbatts/tar-split/tar/storage/packer.go | 13 ++++++-- + 8 files changed, 101 insertions(+), 13 deletions(-) + +diff --git a/hack/vendor.sh b/hack/vendor.sh +index 68772ef..52ba6ef 100755 +--- a/hack/vendor.sh ++++ b/hack/vendor.sh +@@ -36,7 +36,7 @@ clone git github.com/hashicorp/consul v0.5.2 + + # get graph and distribution packages + clone git github.com/docker/distribution ec87e9b6971d831f0eff752ddb54fb64693e51cd # docker/1.8 branch +-clone git github.com/vbatts/tar-split v0.9.6 ++clone git github.com/vbatts/tar-split v0.9.10 + + clone git github.com/docker/notary 8e8122eb5528f621afcd4e2854c47302f17392f7 + clone git github.com/endophage/gotuf a592b03b28b02bb29bb5878308fb1abed63383b5 +diff --git a/vendor/src/github.com/vbatts/tar-split/archive/tar/common.go b/vendor/src/github.com/vbatts/tar-split/archive/tar/common.go +index e363aa7..c31df06 100644 +--- a/vendor/src/github.com/vbatts/tar-split/archive/tar/common.go ++++ b/vendor/src/github.com/vbatts/tar-split/archive/tar/common.go +@@ -139,8 +139,8 @@ func (fi headerFileInfo) Mode() (mode os.FileMode) { + } + + switch fi.h.Typeflag { +- case TypeLink, TypeSymlink: +- // hard link, symbolic link ++ case TypeSymlink: ++ // symbolic link + mode |= os.ModeSymlink + case TypeChar: + // character device node +@@ -249,6 +249,30 @@ func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { + if fm&os.ModeSticky != 0 { + h.Mode |= c_ISVTX + } ++ // If possible, populate additional fields from OS-specific ++ // FileInfo fields. ++ if sys, ok := fi.Sys().(*Header); ok { ++ // This FileInfo came from a Header (not the OS). Use the ++ // original Header to populate all remaining fields. ++ h.Uid = sys.Uid ++ h.Gid = sys.Gid ++ h.Uname = sys.Uname ++ h.Gname = sys.Gname ++ h.AccessTime = sys.AccessTime ++ h.ChangeTime = sys.ChangeTime ++ if sys.Xattrs != nil { ++ h.Xattrs = make(map[string]string) ++ for k, v := range sys.Xattrs { ++ h.Xattrs[k] = v ++ } ++ } ++ if sys.Typeflag == TypeLink { ++ // hard link ++ h.Typeflag = TypeLink ++ h.Size = 0 ++ h.Linkname = sys.Linkname ++ } ++ } + if sysStat != nil { + return h, sysStat(fi, h) + } +diff --git a/vendor/src/github.com/vbatts/tar-split/archive/tar/reader.go b/vendor/src/github.com/vbatts/tar-split/archive/tar/reader.go +index c72e002..4168ea2 100644 +--- a/vendor/src/github.com/vbatts/tar-split/archive/tar/reader.go ++++ b/vendor/src/github.com/vbatts/tar-split/archive/tar/reader.go +@@ -138,7 +138,13 @@ func (tr *Reader) Next() (*Header, error) { + // We actually read the whole file, + // but this skips alignment padding + tr.skipUnread() ++ if tr.err != nil { ++ return nil, tr.err ++ } + hdr = tr.readHeader() ++ if hdr == nil { ++ return nil, tr.err ++ } + mergePAX(hdr, headers) + + // Check for a PAX format sparse file +@@ -397,7 +403,7 @@ func parsePAX(r io.Reader) (map[string]string, error) { + } + // Parse the first token as a decimal integer. + n, err := strconv.ParseInt(string(buf[:sp]), 10, 0) +- if err != nil { ++ if err != nil || n < 5 || int64(len(buf)) < n { + return nil, ErrHeader + } + // Extract everything between the decimal and the n -1 on the +@@ -553,6 +559,10 @@ func (tr *Reader) readHeader() *Header { + hdr.Uid = int(tr.octal(s.next(8))) + hdr.Gid = int(tr.octal(s.next(8))) + hdr.Size = tr.octal(s.next(12)) ++ if hdr.Size < 0 { ++ tr.err = ErrHeader ++ return nil ++ } + hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0) + s.next(8) // chksum + hdr.Typeflag = s.next(1)[0] +@@ -895,6 +905,9 @@ func (sfr *sparseFileReader) Read(b []byte) (n int, err error) { + // Otherwise, we're at the end of the file + return 0, io.EOF + } ++ if sfr.tot < sfr.sp[0].offset { ++ return 0, io.ErrUnexpectedEOF ++ } + if sfr.pos < sfr.sp[0].offset { + // We're in a hole + n = sfr.readHole(b, sfr.sp[0].offset) +diff --git a/vendor/src/github.com/vbatts/tar-split/archive/tar/writer.go b/vendor/src/github.com/vbatts/tar-split/archive/tar/writer.go +index dafb2ca..9dbc01a 100644 +--- a/vendor/src/github.com/vbatts/tar-split/archive/tar/writer.go ++++ b/vendor/src/github.com/vbatts/tar-split/archive/tar/writer.go +@@ -355,7 +355,7 @@ func paxHeader(msg string) string { + // hdr.Size bytes are written after WriteHeader. + func (tw *Writer) Write(b []byte) (n int, err error) { + if tw.closed { +- err = ErrWriteTooLong ++ err = ErrWriteAfterClose + return + } + overwrite := false +diff --git a/vendor/src/github.com/vbatts/tar-split/tar/asm/assemble.go b/vendor/src/github.com/vbatts/tar-split/tar/asm/assemble.go +index 74317cb..83d6426 100644 +--- a/vendor/src/github.com/vbatts/tar-split/tar/asm/assemble.go ++++ b/vendor/src/github.com/vbatts/tar-split/tar/asm/assemble.go +@@ -39,7 +39,7 @@ func NewOutputTarStream(fg storage.FileGetter, up storage.Unpacker) io.ReadClose + if entry.Size == 0 { + continue + } +- fh, err := fg.Get(entry.Name) ++ fh, err := fg.Get(entry.GetName()) + if err != nil { + pw.CloseWithError(err) + return +@@ -56,7 +56,7 @@ func NewOutputTarStream(fg storage.FileGetter, up storage.Unpacker) io.ReadClose + // but since it's coming through the PipeReader, the context of + // _which_ file would be lost... + fh.Close() +- pw.CloseWithError(fmt.Errorf("file integrity checksum failed for %q", entry.Name)) ++ pw.CloseWithError(fmt.Errorf("file integrity checksum failed for %q", entry.GetName())) + return + } + fh.Close() +diff --git a/vendor/src/github.com/vbatts/tar-split/tar/asm/disassemble.go b/vendor/src/github.com/vbatts/tar-split/tar/asm/disassemble.go +index 7986890..54ef23a 100644 +--- a/vendor/src/github.com/vbatts/tar-split/tar/asm/disassemble.go ++++ b/vendor/src/github.com/vbatts/tar-split/tar/asm/disassemble.go +@@ -92,13 +92,16 @@ func NewInputTarStream(r io.Reader, p storage.Packer, fp storage.FilePutter) (io + } + } + +- // File entries added, regardless of size +- _, err = p.AddEntry(storage.Entry{ ++ entry := storage.Entry{ + Type: storage.FileType, +- Name: hdr.Name, + Size: hdr.Size, + Payload: csum, +- }) ++ } ++ // For proper marshalling of non-utf8 characters ++ entry.SetName(hdr.Name) ++ ++ // File entries added, regardless of size ++ _, err = p.AddEntry(entry) + if err != nil { + pW.CloseWithError(err) + return +diff --git a/vendor/src/github.com/vbatts/tar-split/tar/storage/entry.go b/vendor/src/github.com/vbatts/tar-split/tar/storage/entry.go +index 38fe7ba..c91e7ea 100644 +--- a/vendor/src/github.com/vbatts/tar-split/tar/storage/entry.go ++++ b/vendor/src/github.com/vbatts/tar-split/tar/storage/entry.go +@@ -1,5 +1,7 @@ + package storage + ++import "unicode/utf8" ++ + // Entries is for sorting by Position + type Entries []Entry + +@@ -33,7 +35,44 @@ const ( + type Entry struct { + Type Type `json:"type"` + Name string `json:"name,omitempty"` ++ NameRaw []byte `json:"name_raw,omitempty"` + Size int64 `json:"size,omitempty"` + Payload []byte `json:"payload"` // SegmentType stores payload here; FileType stores crc64 checksum here; + Position int `json:"position"` + } ++ ++// SetName will check name for valid UTF-8 string, and set the appropriate ++// field. See https://github.com/vbatts/tar-split/issues/17 ++func (e *Entry) SetName(name string) { ++ if utf8.ValidString(name) { ++ e.Name = name ++ } else { ++ e.NameRaw = []byte(name) ++ } ++} ++ ++// SetNameBytes will check name for valid UTF-8 string, and set the appropriate ++// field ++func (e *Entry) SetNameBytes(name []byte) { ++ if utf8.Valid(name) { ++ e.Name = string(name) ++ } else { ++ e.NameRaw = name ++ } ++} ++ ++// GetName returns the string for the entry's name, regardless of the field stored in ++func (e *Entry) GetName() string { ++ if len(e.NameRaw) > 0 { ++ return string(e.NameRaw) ++ } ++ return e.Name ++} ++ ++// GetNameBytes returns the bytes for the entry's name, regardless of the field stored in ++func (e *Entry) GetNameBytes() []byte { ++ if len(e.NameRaw) > 0 { ++ return e.NameRaw ++ } ++ return []byte(e.Name) ++} +diff --git a/vendor/src/github.com/vbatts/tar-split/tar/storage/packer.go b/vendor/src/github.com/vbatts/tar-split/tar/storage/packer.go +index a02a19a..0c9d99b 100644 +--- a/vendor/src/github.com/vbatts/tar-split/tar/storage/packer.go ++++ b/vendor/src/github.com/vbatts/tar-split/tar/storage/packer.go +@@ -6,6 +6,7 @@ import ( + "errors" + "io" + "path/filepath" ++ "unicode/utf8" + ) + + // ErrDuplicatePath occurs when a tar archive has more than one entry for the +@@ -61,7 +62,7 @@ func (jup *jsonUnpacker) Next() (*Entry, error) { + + // check for dup name + if e.Type == FileType { +- cName := filepath.Clean(e.Name) ++ cName := filepath.Clean(e.GetName()) + if _, ok := jup.seen[cName]; ok { + return nil, ErrDuplicatePath + } +@@ -93,9 +94,17 @@ type jsonPacker struct { + type seenNames map[string]struct{} + + func (jp *jsonPacker) AddEntry(e Entry) (int, error) { ++ // if Name is not valid utf8, switch it to raw first. ++ if e.Name != "" { ++ if !utf8.ValidString(e.Name) { ++ e.NameRaw = []byte(e.Name) ++ e.Name = "" ++ } ++ } ++ + // check early for dup name + if e.Type == FileType { +- cName := filepath.Clean(e.Name) ++ cName := filepath.Clean(e.GetName()) + if _, ok := jp.seen[cName]; ok { + return -1, ErrDuplicatePath + } +-- +2.4.3 + |