From 3827376649a88cb690a59131c054f70c05c22f7c Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Wed, 5 Oct 2022 01:41:31 +0900 Subject: [PATCH] MountStubsCleaner: preserve timestamps Fix issue 3148 Signed-off-by: Akihiro Suda --- client/client_test.go | 56 +++++++++++++++++++ executor/stubs.go | 23 +++++++- go.mod | 1 + go.sum | 2 + vendor/github.com/djherbis/atime/.travis.yml | 22 ++++++++ vendor/github.com/djherbis/atime/LICENSE | 22 ++++++++ vendor/github.com/djherbis/atime/README.md | 42 ++++++++++++++ .../github.com/djherbis/atime/atime_darwin.go | 21 +++++++ .../djherbis/atime/atime_dragonfly.go | 21 +++++++ .../djherbis/atime/atime_freebsd.go | 21 +++++++ .../github.com/djherbis/atime/atime_linux.go | 21 +++++++ .../github.com/djherbis/atime/atime_nacl.go | 22 ++++++++ .../github.com/djherbis/atime/atime_netbsd.go | 21 +++++++ .../djherbis/atime/atime_openbsd.go | 21 +++++++ .../github.com/djherbis/atime/atime_plan9.go | 16 ++++++ .../djherbis/atime/atime_solaris.go | 21 +++++++ .../github.com/djherbis/atime/atime_wasm.go | 22 ++++++++ .../djherbis/atime/atime_windows.go | 17 ++++++ vendor/github.com/djherbis/atime/stat.go | 21 +++++++ vendor/modules.txt | 3 + 20 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/djherbis/atime/.travis.yml create mode 100644 vendor/github.com/djherbis/atime/LICENSE create mode 100644 vendor/github.com/djherbis/atime/README.md create mode 100644 vendor/github.com/djherbis/atime/atime_darwin.go create mode 100644 vendor/github.com/djherbis/atime/atime_dragonfly.go create mode 100644 vendor/github.com/djherbis/atime/atime_freebsd.go create mode 100644 vendor/github.com/djherbis/atime/atime_linux.go create mode 100644 vendor/github.com/djherbis/atime/atime_nacl.go create mode 100644 vendor/github.com/djherbis/atime/atime_netbsd.go create mode 100644 vendor/github.com/djherbis/atime/atime_openbsd.go create mode 100644 vendor/github.com/djherbis/atime/atime_plan9.go create mode 100644 vendor/github.com/djherbis/atime/atime_solaris.go create mode 100644 vendor/github.com/djherbis/atime/atime_wasm.go create mode 100644 vendor/github.com/djherbis/atime/atime_windows.go create mode 100644 vendor/github.com/djherbis/atime/stat.go diff --git a/client/client_test.go b/client/client_test.go index e98014f96abec..74a049f7c9dc8 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -176,6 +176,7 @@ func TestIntegration(t *testing.T) { testExportAnnotationsMediaTypes, testExportAttestations, testAttestationDefaultSubject, + testMountStubsTimestamp, ) tests = append(tests, diffOpTestCases()...) integration.Run(t, tests, mirrors) @@ -6769,6 +6770,61 @@ func testAttestationDefaultSubject(t *testing.T, sb integration.Sandbox) { } } +// https://github.com/moby/buildkit/issues/3148 +func testMountStubsTimestamp(t *testing.T, sb integration.Sandbox) { + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + const sourceDateEpoch = int64(1234567890) // Fri Feb 13 11:31:30 PM UTC 2009 + st := llb.Image("busybox:latest").Run( + llb.Args([]string{"/bin/touch", fmt.Sprintf("--date=@%d", sourceDateEpoch), "/bin", "/etc"}), + ) + def, err := st.Marshal(sb.Context()) + require.NoError(t, err) + + tmpDir := t.TempDir() + tarFile := filepath.Join(tmpDir, "out.tar") + tarFileW, err := os.Create(tarFile) + require.NoError(t, err) + defer tarFileW.Close() + + _, err = c.Solve(sb.Context(), def, SolveOpt{ + Exports: []ExportEntry{ + { + Type: ExporterTar, + Output: fixedWriteCloser(tarFileW), + }, + }, + }, nil) + require.NoError(t, err) + tarFileW.Close() + + tarFileR, err := os.Open(tarFile) + require.NoError(t, err) + defer tarFileR.Close() + tarR := tar.NewReader(tarFileR) + touched := map[string]*tar.Header{ + "bin/": nil, + "etc/": nil, + } + for { + hd, err := tarR.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + if x, ok := touched[hd.Name]; ok && x == nil { + touched[hd.Name] = hd + } + } + for name, hd := range touched { + t.Logf("Verifying %q (%+v)", name, hd) + require.NotNil(t, hd, name) + require.Equal(t, sourceDateEpoch, hd.ModTime.Unix(), name) + } +} + func makeSSHAgentSock(t *testing.T, agent agent.Agent) (p string, err error) { tmpDir, err := integration.Tmpdir(t) if err != nil { diff --git a/executor/stubs.go b/executor/stubs.go index 2c13b13053a4f..65259a76de67a 100644 --- a/executor/stubs.go +++ b/executor/stubs.go @@ -7,6 +7,8 @@ import ( "syscall" "github.com/containerd/continuity/fs" + "github.com/djherbis/atime" + "github.com/sirupsen/logrus" ) func MountStubsCleaner(dir string, mounts []Mount) func() { @@ -43,7 +45,26 @@ func MountStubsCleaner(dir string, mounts []Mount) func() { if st.Size() != 0 { continue } - os.Remove(p) + // Back up the timestamps of the dir for reproducible builds + // https://github.com/moby/buildkit/issues/3148 + dir := filepath.Dir(p) + dirSt, err := os.Stat(dir) + if err != nil { + logrus.WithError(err).Warnf("Failed to stat %q (parent of %q)", dir, p) + continue + } + atime := atime.Get(dirSt) + mtime := dirSt.ModTime() + + if err := os.RemoveAll(p); err != nil { + logrus.WithError(err).Warnf("Failed to remove %q", p) + continue + } + + // Revert the timestamps of the dir + if err := os.Chtimes(dir, atime, mtime); err != nil { + logrus.WithError(err).Warnf("Failed to os.Chtimes(%q, %v, %v)", dir, atime, mtime) + } } } } diff --git a/go.mod b/go.mod index d1cf36f59b26a..2ae01dd2b107a 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.12.0 github.com/containerd/typeurl v1.0.2 github.com/coreos/go-systemd/v22 v22.3.2 + github.com/djherbis/atime v1.1.0 github.com/docker/cli v20.10.17+incompatible github.com/docker/distribution v2.8.1+incompatible github.com/docker/docker v20.10.17+incompatible // v22.06.x - see "replace" for the actual version diff --git a/go.sum b/go.sum index bdfeda97c8742..bc3c2045a92ab 100644 --- a/go.sum +++ b/go.sum @@ -485,6 +485,8 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/djherbis/atime v1.1.0 h1:rgwVbP/5by8BvvjBNrbh64Qz33idKT3pSnMSJsxhi0g= +github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= diff --git a/vendor/github.com/djherbis/atime/.travis.yml b/vendor/github.com/djherbis/atime/.travis.yml new file mode 100644 index 0000000000000..3512f112f5fc2 --- /dev/null +++ b/vendor/github.com/djherbis/atime/.travis.yml @@ -0,0 +1,22 @@ +language: go +go: +- tip +before_install: +- go get -u golang.org/x/lint/golint +- go get github.com/axw/gocov/gocov +- go get github.com/mattn/goveralls +- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; + fi +script: +- '[ "${TRAVIS_PULL_REQUEST}" != "false" ] || $HOME/gopath/bin/goveralls -service=travis-ci + -repotoken $COVERALLS_TOKEN' +- "$HOME/gopath/bin/golint ./..." +- go vet +- go test -bench=.* -v ./... +notifications: + email: + on_success: never + on_failure: change +env: + global: + secure: MnwmqgePnDx0t5Ehrp2kLqnuaOh8JxQ9ebaA3KFE3q1TAGVJlv6mHJGFsP+gtC6DIpqosPlZf4JeuMHN+9eLOpa/8E+c9Rl0gctgMpZssgNEAfsqwy7+KBhqLsWKa9NetSB3ZhZit9KLknx8AmjpQCYUcc+n2KN6W4bkbebIp3o= diff --git a/vendor/github.com/djherbis/atime/LICENSE b/vendor/github.com/djherbis/atime/LICENSE new file mode 100644 index 0000000000000..1e7b7cc09f1f3 --- /dev/null +++ b/vendor/github.com/djherbis/atime/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Dustin H + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/djherbis/atime/README.md b/vendor/github.com/djherbis/atime/README.md new file mode 100644 index 0000000000000..e707278fdd845 --- /dev/null +++ b/vendor/github.com/djherbis/atime/README.md @@ -0,0 +1,42 @@ +atime +========== + +[![GoDoc](https://godoc.org/github.com/djherbis/atime?status.svg)](https://godoc.org/github.com/djherbis/atime) +[![Release](https://img.shields.io/github/release/djherbis/atime.svg)](https://github.com/djherbis/atime/releases/latest) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.txt) +[![Build Status](https://travis-ci.org/djherbis/atime.svg?branch=master)](https://travis-ci.org/djherbis/atime) +[![Coverage Status](https://coveralls.io/repos/djherbis/atime/badge.svg?branch=master)](https://coveralls.io/r/djherbis/atime?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/djherbis/atime)](https://goreportcard.com/report/github.com/djherbis/atime) +[![Sourcegraph](https://sourcegraph.com/github.com/djherbis/atime/-/badge.svg)](https://sourcegraph.com/github.com/djherbis/atime?badge) + +Usage +------------ +File Access Times for #golang + +Looking for ctime or btime? Checkout https://github.com/djherbis/times + +Go has a hidden atime function for most platforms, this repo makes it accessible. + +```go +package main + +import ( + "log" + + "github.com/djherbis/atime" +) + +func main() { + at, err := atime.Stat("myfile") + if err != nil { + log.Fatal(err.Error()) + } + log.Println(at) +} +``` + +Installation +------------ +```sh +go get github.com/djherbis/atime +``` diff --git a/vendor/github.com/djherbis/atime/atime_darwin.go b/vendor/github.com/djherbis/atime/atime_darwin.go new file mode 100644 index 0000000000000..ccf7ebc306618 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_darwin.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_darwin.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec) +} diff --git a/vendor/github.com/djherbis/atime/atime_dragonfly.go b/vendor/github.com/djherbis/atime/atime_dragonfly.go new file mode 100644 index 0000000000000..cd7619e6c1201 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_dragonfly.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_dragonfly.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim) +} diff --git a/vendor/github.com/djherbis/atime/atime_freebsd.go b/vendor/github.com/djherbis/atime/atime_freebsd.go new file mode 100644 index 0000000000000..ec7bb8b5d2716 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_freebsd.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_freebsd.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec) +} diff --git a/vendor/github.com/djherbis/atime/atime_linux.go b/vendor/github.com/djherbis/atime/atime_linux.go new file mode 100644 index 0000000000000..b8827bb3e77ad --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_linux.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_linux.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim) +} diff --git a/vendor/github.com/djherbis/atime/atime_nacl.go b/vendor/github.com/djherbis/atime/atime_nacl.go new file mode 100644 index 0000000000000..ed257513aebc4 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_nacl.go @@ -0,0 +1,22 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_nacl.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(sec, nsec int64) time.Time { + return time.Unix(sec, nsec) +} + +func atime(fi os.FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + return timespecToTime(st.Atime, st.AtimeNsec) +} diff --git a/vendor/github.com/djherbis/atime/atime_netbsd.go b/vendor/github.com/djherbis/atime/atime_netbsd.go new file mode 100644 index 0000000000000..6919d05a5a983 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_netbsd.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_netbsd.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec) +} diff --git a/vendor/github.com/djherbis/atime/atime_openbsd.go b/vendor/github.com/djherbis/atime/atime_openbsd.go new file mode 100644 index 0000000000000..3188a0738d631 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_openbsd.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_openbsd.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim) +} diff --git a/vendor/github.com/djherbis/atime/atime_plan9.go b/vendor/github.com/djherbis/atime/atime_plan9.go new file mode 100644 index 0000000000000..1b3bb972aca59 --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_plan9.go @@ -0,0 +1,16 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_plan9.go + +package atime + +import ( + "os" + "time" +) + +func atime(fi os.FileInfo) time.Time { + return time.Unix(int64(fi.Sys().(*syscall.Dir).Atime), 0) +} diff --git a/vendor/github.com/djherbis/atime/atime_solaris.go b/vendor/github.com/djherbis/atime/atime_solaris.go new file mode 100644 index 0000000000000..28175a7ddbe5d --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_solaris.go @@ -0,0 +1,21 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_solaris.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(ts syscall.Timespec) time.Time { + return time.Unix(int64(ts.Sec), int64(ts.Nsec)) +} + +func atime(fi os.FileInfo) time.Time { + return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim) +} diff --git a/vendor/github.com/djherbis/atime/atime_wasm.go b/vendor/github.com/djherbis/atime/atime_wasm.go new file mode 100644 index 0000000000000..923b7a9d4748c --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_wasm.go @@ -0,0 +1,22 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_js.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func timespecToTime(sec, nsec int64) time.Time { + return time.Unix(sec, nsec) +} + +func atime(fi os.FileInfo) time.Time { + stat := fi.Sys().(*syscall.Stat_t) + return timespecToTime(stat.Atime, stat.AtimeNsec) +} diff --git a/vendor/github.com/djherbis/atime/atime_windows.go b/vendor/github.com/djherbis/atime/atime_windows.go new file mode 100644 index 0000000000000..8a15146fd249a --- /dev/null +++ b/vendor/github.com/djherbis/atime/atime_windows.go @@ -0,0 +1,17 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// http://golang.org/src/os/stat_windows.go + +package atime + +import ( + "os" + "syscall" + "time" +) + +func atime(fi os.FileInfo) time.Time { + return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) +} diff --git a/vendor/github.com/djherbis/atime/stat.go b/vendor/github.com/djherbis/atime/stat.go new file mode 100644 index 0000000000000..eb658e1444fdc --- /dev/null +++ b/vendor/github.com/djherbis/atime/stat.go @@ -0,0 +1,21 @@ +// Package atime provides a platform-independent way to get atimes for files. +package atime + +import ( + "os" + "time" +) + +// Get returns the Last Access Time for the given FileInfo +func Get(fi os.FileInfo) time.Time { + return atime(fi) +} + +// Stat returns the Last Access Time for the given filename +func Stat(name string) (time.Time, error) { + fi, err := os.Stat(name) + if err != nil { + return time.Time{}, err + } + return atime(fi), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 592e89319257c..0b2977f045f89 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -365,6 +365,9 @@ github.com/davecgh/go-spew/spew # github.com/dimchansky/utfbom v1.1.1 ## explicit github.com/dimchansky/utfbom +# github.com/djherbis/atime v1.1.0 +## explicit; go 1.16 +github.com/djherbis/atime # github.com/docker/cli v20.10.17+incompatible ## explicit github.com/docker/cli/cli/config