Update archive to use time operations directly

Update archive time logic to mirror containerd's

Signed-off-by: Derek McGowan <derek@mcg.dev>
This commit is contained in:
Derek McGowan
2024-12-11 15:28:25 -08:00
parent 64ca1c0f92
commit 1b4cbea3a8
9 changed files with 122 additions and 51 deletions

View File

@@ -822,26 +822,22 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
return err
}
aTime := hdr.AccessTime
if aTime.Before(hdr.ModTime) {
// Last access time should never be before last modified time.
aTime = hdr.ModTime
}
aTime := boundTime(latestTime(hdr.AccessTime, hdr.ModTime))
mTime := boundTime(hdr.ModTime)
// system.Chtimes doesn't support a NOFOLLOW flag atm
// chtimes doesn't support a NOFOLLOW flag atm
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := system.Chtimes(path, aTime, hdr.ModTime); err != nil {
if err := chtimes(path, aTime, mTime); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := system.Chtimes(path, aTime, hdr.ModTime); err != nil {
if err := chtimes(path, aTime, mTime); err != nil {
return err
}
} else {
ts := []syscall.Timespec{timeToTimespec(aTime), timeToTimespec(hdr.ModTime)}
if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
if err := lchtimes(path, aTime, mTime); err != nil {
return err
}
}
@@ -1201,7 +1197,7 @@ loop:
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name)
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
return err
}
}

View File

@@ -12,7 +12,6 @@ import (
"time"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/system"
"gotest.tools/v3/assert"
"gotest.tools/v3/skip"
)
@@ -107,7 +106,7 @@ func provisionSampleDir(t *testing.T, root string, files []FileData) {
if info.filetype != Symlink {
// Set a consistent ctime, atime for all files and dirs
err := system.Chtimes(p, now, now)
err := chtimes(p, now, now)
assert.NilError(t, err)
}
}
@@ -292,7 +291,7 @@ func mutateSampleDir(t *testing.T, root string) {
assert.NilError(t, err)
// Touch file
err = system.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second))
err = chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second))
assert.NilError(t, err)
// Replace file with dir
@@ -327,7 +326,7 @@ func mutateSampleDir(t *testing.T, root string) {
assert.NilError(t, err)
// Touch dir
err = system.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second))
err = chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second))
assert.NilError(t, err)
}

View File

@@ -10,7 +10,6 @@ import (
"strings"
"github.com/containerd/log"
"github.com/docker/docker/pkg/system"
)
// Errors used or returned by this file.
@@ -203,7 +202,7 @@ func CopyInfoDestinationPath(path string) (info CopyInfo, err error) {
return CopyInfo{}, err
}
if !system.IsAbs(linkTarget) {
if !filepath.IsAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := SplitPathDirEntry(path)
linkTarget = filepath.Join(dstParent, linkTarget)

View File

@@ -12,7 +12,6 @@ import (
"github.com/containerd/log"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
@@ -200,7 +199,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
for _, hdr := range dirs {
// #nosec G305 -- The header was checked for path traversal before it was appended to the dirs slice.
path := filepath.Join(dest, hdr.Name)
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
if err := chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
return 0, err
}
}

38
pkg/archive/time.go Normal file
View File

@@ -0,0 +1,38 @@
package archive // import "github.com/docker/docker/pkg/archive"
import (
"syscall"
"time"
"unsafe"
)
var (
minTime = time.Unix(0, 0)
maxTime time.Time
)
func init() {
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
// This is a 64 bit timespec
// os.Chtimes limits time to the following
maxTime = time.Unix(0, 1<<63-1)
} else {
// This is a 32 bit timespec
maxTime = time.Unix(1<<31-1, 0)
}
}
func boundTime(t time.Time) time.Time {
if t.Before(minTime) || t.After(maxTime) {
return minTime
}
return t
}
func latestTime(t1, t2 time.Time) time.Time {
if t1.Before(t2) {
return t2
}
return t1
}

View File

@@ -1,16 +0,0 @@
package archive // import "github.com/docker/docker/pkg/archive"
import (
"syscall"
"time"
)
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())
}

View File

@@ -0,0 +1,40 @@
//go:build !windows
package archive // import "github.com/docker/docker/pkg/archive"
import (
"os"
"time"
"golang.org/x/sys/unix"
)
// chtimes changes the access time and modified time of a file at the given path.
// If the modified time is prior to the Unix Epoch (unixMinTime), or after the
// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this
// case, Chtimes defaults to Unix Epoch, just in case.
func chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
func timeToTimespec(time time.Time) (ts unix.Timespec) {
if time.IsZero() {
// Return UTIME_OMIT special value
ts.Sec = 0
ts.Nsec = (1 << 30) - 2
return
}
return unix.NsecToTimespec(time.UnixNano())
}
func lchtimes(name string, atime time.Time, mtime time.Time) error {
utimes := [2]unix.Timespec{
timeToTimespec(atime),
timeToTimespec(mtime),
}
err := unix.UtimesNanoAt(unix.AT_FDCWD, name, utimes[0:], unix.AT_SYMLINK_NOFOLLOW)
if err != nil && err != unix.ENOSYS {
return err
}
return err
}

View File

@@ -1,16 +0,0 @@
//go:build !linux
package archive // import "github.com/docker/docker/pkg/archive"
import (
"syscall"
"time"
)
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
nsec := int64(0)
if !time.IsZero() {
nsec = time.UnixNano()
}
return syscall.NsecToTimespec(nsec)
}

View File

@@ -0,0 +1,32 @@
package archive // import "github.com/docker/docker/pkg/archive"
import (
"os"
"time"
"golang.org/x/sys/windows"
)
func chtimes(name string, atime time.Time, mtime time.Time) error {
if err := os.Chtimes(name, atime, mtime); err != nil {
return err
}
pathp, err := windows.UTF16PtrFromString(name)
if err != nil {
return err
}
h, err := windows.CreateFile(pathp,
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil {
return err
}
defer windows.Close(h)
c := windows.NsecToFiletime(mtime.UnixNano())
return windows.SetFileTime(h, &c, nil, nil)
}
func lchtimes(name string, atime time.Time, mtime time.Time) error {
return nil
}