Skip to content

Commit

Permalink
MountStubsCleaner: preserve timestamps
Browse files Browse the repository at this point in the history
Fix issue 3148

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Nov 3, 2022
1 parent 60c9d2c commit 78bb1fa
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
56 changes: 56 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ func TestIntegration(t *testing.T) {
testAttestationBundle,
testSBOMScan,
testSBOMScanSingleRef,
testMountStubsTimestamp,
)
tests = append(tests, diffOpTestCases()...)
integration.Run(t, tests, mirrors)
Expand Down Expand Up @@ -7759,6 +7760,61 @@ EOF
require.Equal(t, map[string]interface{}{"success": false}, attest.Predicate)
}

// 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 {
Expand Down
26 changes: 25 additions & 1 deletion executor/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"syscall"

"github.com/containerd/continuity/fs"
"github.com/moby/buildkit/util/system"
"github.com/sirupsen/logrus"
)

func MountStubsCleaner(dir string, mounts []Mount) func() {
Expand Down Expand Up @@ -43,7 +45,29 @@ 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 mount stub %q)", dir, p)
continue
}
mtime := dirSt.ModTime()
atime, err := system.Atime(dirSt)
if err != nil {
logrus.WithError(err).Warnf("Failed to stat atime of %q (parent of mount stub %q)", dir, p)
atime = mtime
}

if err := os.Remove(p); err != nil {
logrus.WithError(err).Warnf("Failed to remove mount stub %q", p)
}

// Restore the timestamps of the dir
if err := os.Chtimes(dir, atime, mtime); err != nil {
logrus.WithError(err).Warnf("Failed to restore time time mount stub timestamp (os.Chtimes(%q, %v, %v))", dir, atime, mtime)
}
}
}
}
21 changes: 21 additions & 0 deletions util/system/atime_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !windows
// +build !windows

package system

import (
"fmt"
iofs "io/fs"
"syscall"
"time"

"github.com/containerd/continuity/fs"
)

func Atime(st iofs.FileInfo) (time.Time, error) {
stSys, ok := st.Sys().(*syscall.Stat_t)
if !ok {
return time.Time{}, fmt.Errorf("expected st.Sys() to be *syscall.Stat_t, got %T", st.Sys())
}
return fs.StatATimeAsTime(stSys), nil
}
17 changes: 17 additions & 0 deletions util/system/atime_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package system

import (
"fmt"
iofs "io/fs"
"syscall"
"time"
)

func Atime(st iofs.FileInfo) (time.Time, error) {
stSys, ok := st.Sys().(*syscall.Win32FileAttributeData)
if !ok {
return time.Time{}, fmt.Errorf("expected st.Sys() to be *syscall.Win32FileAttributeData, got %T", st.Sys())
}
// ref: https://github.com/golang/go/blob/go1.19.2/src/os/types_windows.go#L230
return time.Unix(0, stSys.LastAccessTime.Nanoseconds()), nil
}

0 comments on commit 78bb1fa

Please sign in to comment.