Merge pull request #44265 from thaJeztah/pkg_system_move_init_step1

pkg/system: cleanup, test-fixes and improvements and minor fixes
This commit is contained in:
Sebastiaan van Stijn 2022-10-15 21:28:13 +02:00 committed by GitHub
commit 081c00c7df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 278 additions and 232 deletions

View file

@ -2,24 +2,41 @@ package system // import "github.com/docker/docker/pkg/system"
import (
"os"
"syscall"
"time"
"unsafe"
)
// Chtimes changes the access time and modified time of a file at the given path
// Used by Chtimes
var unixEpochTime, unixMaxTime time.Time
func init() {
unixEpochTime = time.Unix(0, 0)
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
// This is a 64 bit timespec
// os.Chtimes limits time to the following
//
// Note that this intentionally sets nsec (not sec), which sets both sec
// and nsec internally in time.Unix();
// https://github.com/golang/go/blob/go1.19.2/src/time/time.go#L1364-L1380
unixMaxTime = time.Unix(0, 1<<63-1)
} else {
// This is a 32 bit timespec
unixMaxTime = time.Unix(1<<31-1, 0)
}
}
// 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 {
unixMinTime := time.Unix(0, 0)
unixMaxTime := maxTime
// If the modified time is prior to the Unix Epoch, or after the
// end of Unix Time, os.Chtimes has undefined behavior
// default to Unix Epoch in this case, just in case
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
atime = unixMinTime
if atime.Before(unixEpochTime) || atime.After(unixMaxTime) {
atime = unixEpochTime
}
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
mtime = unixMinTime
if mtime.Before(unixEpochTime) || mtime.After(unixMaxTime) {
mtime = unixEpochTime
}
if err := os.Chtimes(name, atime, mtime); err != nil {

View file

@ -2,88 +2,109 @@ package system // import "github.com/docker/docker/pkg/system"
import (
"os"
"path/filepath"
"syscall"
"testing"
"time"
)
// TestChtimesLinux tests Chtimes access time on a tempfile on Linux
func TestChtimesLinux(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
// TestChtimesATime tests Chtimes access time on a tempfile.
func TestChtimesATime(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(stat.Atim.Unix())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
})
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
stat = f.Sys().(*syscall.Stat_t)
aTime = time.Unix(stat.Atim.Unix())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
stat := f.Sys().(*syscall.Stat_t)
aTime := time.Unix(stat.Atim.Unix())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
})
}

View file

@ -7,87 +7,94 @@ import (
"time"
)
// prepareTempFile creates a temporary file in a temporary directory.
func prepareTempFile(t *testing.T) (string, string) {
dir, err := os.MkdirTemp("", "docker-system-test")
if err != nil {
// TestChtimesModTime tests Chtimes on a tempfile. Test only mTime, because
// aTime is OS dependent.
func TestChtimesModTime(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
file := filepath.Join(dir, "exist")
if err := os.WriteFile(file, []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
return file, dir
}
// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent
func TestChtimes(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
if f.ModTime() != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime() != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
}
if f.ModTime() != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
}
})
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
}
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
}
})
}

View file

@ -9,18 +9,17 @@ import (
// setCTime will set the create time on a file. On Windows, this requires
// calling SetFileTime and explicitly including the create time.
func setCTime(path string, ctime time.Time) error {
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
pathp, e := windows.UTF16PtrFromString(path)
if e != nil {
return e
pathp, err := windows.UTF16PtrFromString(path)
if err != nil {
return err
}
h, e := windows.CreateFile(pathp,
h, err := windows.CreateFile(pathp,
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
if e != nil {
return e
if err != nil {
return err
}
defer windows.Close(h)
c := windows.NsecToFiletime(windows.TimespecToNsec(ctimespec))
c := windows.NsecToFiletime(ctime.UnixNano())
return windows.SetFileTime(h, &c, nil, nil)
}

View file

@ -5,83 +5,104 @@ package system // import "github.com/docker/docker/pkg/system"
import (
"os"
"path/filepath"
"syscall"
"testing"
"time"
)
// TestChtimesWindows tests Chtimes access time on a tempfile on Windows
func TestChtimesWindows(t *testing.T) {
file, dir := prepareTempFile(t)
defer os.RemoveAll(dir)
// TestChtimesATimeWindows tests Chtimes access time on a tempfile on Windows.
func TestChtimesATimeWindows(t *testing.T) {
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
unixEpochTime := time.Unix(0, 0)
afterUnixEpochTime := time.Unix(100, 0)
unixMaxTime := maxTime
beforeUnixEpochTime := unixEpochTime.Add(-100 * time.Second)
afterUnixEpochTime := unixEpochTime.Add(100 * time.Second)
// Test both aTime and mTime set to Unix Epoch
Chtimes(file, unixEpochTime, unixEpochTime)
t.Run("both aTime and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime before Unix Epoch and mTime set to Unix Epoch
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
t.Run("aTime before Unix Epoch and mTime set to Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, beforeUnixEpochTime, unixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test aTime set to Unix Epoch and mTime before Unix Epoch
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
t.Run("aTime set to Unix Epoch and mTime before Unix Epoch", func(t *testing.T) {
if err := Chtimes(file, unixEpochTime, beforeUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != unixEpochTime {
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
}
})
// Test both aTime and mTime set to after Unix Epoch (valid time)
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
t.Run("both aTime and mTime set to after Unix Epoch (valid time)", func(t *testing.T) {
if err := Chtimes(file, afterUnixEpochTime, afterUnixEpochTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime != afterUnixEpochTime {
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
}
})
// Test both aTime and mTime set to Unix max time
Chtimes(file, unixMaxTime, unixMaxTime)
t.Run("both aTime and mTime set to Unix max time", func(t *testing.T) {
if err := Chtimes(file, unixMaxTime, unixMaxTime); err != nil {
t.Error(err)
}
f, err = os.Stat(file)
if err != nil {
t.Fatal(err)
}
f, err := os.Stat(file)
if err != nil {
t.Fatal(err)
}
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
}
})
}

View file

@ -1,22 +0,0 @@
package system // import "github.com/docker/docker/pkg/system"
import (
"syscall"
"time"
"unsafe"
)
// Used by chtimes
var maxTime time.Time
func init() {
// chtimes initialization
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)
}
}

View file

@ -5,13 +5,17 @@ package system // import "github.com/docker/docker/pkg/system"
import (
"os"
"path/filepath"
"testing"
)
// TestLstat tests Lstat for existing and non existing files
func TestLstat(t *testing.T) {
file, invalid, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
tmpDir := t.TempDir()
file := filepath.Join(tmpDir, "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
statFile, err := Lstat(file)
if err != nil {
@ -21,7 +25,7 @@ func TestLstat(t *testing.T) {
t.Fatal("returned empty stat for existing file")
}
statInvalid, err := Lstat(invalid)
statInvalid, err := Lstat(filepath.Join(tmpDir, "nosuchfile"))
if err == nil {
t.Fatal("did not return error for non-existing file")
}

View file

@ -5,6 +5,7 @@ package system // import "github.com/docker/docker/pkg/system"
import (
"os"
"path/filepath"
"syscall"
"testing"
@ -13,8 +14,10 @@ import (
// TestFromStatT tests fromStatT for a tempfile
func TestFromStatT(t *testing.T) {
file, _, _, dir := prepareFiles(t)
defer os.RemoveAll(dir)
file := filepath.Join(t.TempDir(), "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
stat := &syscall.Stat_t{}
err := syscall.Lstat(file, stat)

View file

@ -11,30 +11,26 @@ import (
)
// prepareFiles creates files for testing in the temp directory
func prepareFiles(t *testing.T) (string, string, string, string) {
dir, err := os.MkdirTemp("", "docker-system-test")
if err != nil {
func prepareFiles(t *testing.T) (file, invalid, symlink string) {
t.Helper()
dir := t.TempDir()
file = filepath.Join(dir, "exist")
if err := os.WriteFile(file, []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
file := filepath.Join(dir, "exist")
if err := os.WriteFile(file, []byte("hello"), 0644); err != nil {
t.Fatal(err)
}
invalid := filepath.Join(dir, "doesnt-exist")
symlink := filepath.Join(dir, "symlink")
invalid = filepath.Join(dir, "doesnt-exist")
symlink = filepath.Join(dir, "symlink")
if err := os.Symlink(file, symlink); err != nil {
t.Fatal(err)
}
return file, invalid, symlink, dir
return file, invalid, symlink
}
func TestLUtimesNano(t *testing.T) {
file, invalid, symlink, dir := prepareFiles(t)
defer os.RemoveAll(dir)
file, invalid, symlink := prepareFiles(t)
before, err := os.Stat(file)
if err != nil {