Skip to content

Commit 134fefb

Browse files
author
Jess Frazelle
committed
Merge pull request moby#16490 from Microsoft/10662-mtimefix
Fixed file modified time not changing on windows
2 parents 698e149 + 40b77af commit 134fefb

File tree

16 files changed

+412
-45
lines changed

16 files changed

+412
-45
lines changed

builder/internals.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"runtime"
1717
"sort"
1818
"strings"
19-
"syscall"
2019
"time"
2120

2221
"github.com/Sirupsen/logrus"
@@ -342,23 +341,19 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
342341

343342
// Set the mtime to the Last-Modified header value if present
344343
// Otherwise just remove atime and mtime
345-
times := make([]syscall.Timespec, 2)
344+
mTime := time.Time{}
346345

347346
lastMod := resp.Header.Get("Last-Modified")
348347
if lastMod != "" {
349-
mTime, err := http.ParseTime(lastMod)
350348
// If we can't parse it then just let it default to 'zero'
351349
// otherwise use the parsed time value
352-
if err == nil {
353-
times[1] = syscall.NsecToTimespec(mTime.UnixNano())
350+
if parsedMTime, err := http.ParseTime(lastMod); err == nil {
351+
mTime = parsedMTime
354352
}
355353
}
356354

357-
// Windows does not support UtimesNano.
358-
if runtime.GOOS != "windows" {
359-
if err := system.UtimesNano(tmpFileName, times); err != nil {
360-
return err
361-
}
355+
if err := system.Chtimes(tmpFileName, time.Time{}, mTime); err != nil {
356+
return err
362357
}
363358

364359
ci.origPath = filepath.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))

daemon/graphdriver/overlay/copy.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"syscall"
11+
"time"
1112

1213
"github.com/docker/docker/pkg/system"
1314
)
@@ -149,13 +150,15 @@ func copyDir(srcDir, dstDir string, flags copyFlags) error {
149150
}
150151
}
151152

152-
ts := []syscall.Timespec{stat.Atim, stat.Mtim}
153-
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
153+
// system.Chtimes doesn't support a NOFOLLOW flag atm
154154
if !isSymlink {
155-
if err := system.UtimesNano(dstPath, ts); err != nil {
155+
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
156+
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
157+
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
156158
return err
157159
}
158160
} else {
161+
ts := []syscall.Timespec{stat.Atim, stat.Mtim}
159162
if err := system.LUtimesNano(dstPath, ts); err != nil {
160163
return err
161164
}

pkg/archive/archive.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,19 +375,19 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
375375
return err
376376
}
377377

378-
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
379-
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm
378+
// system.Chtimes doesn't support a NOFOLLOW flag atm
380379
if hdr.Typeflag == tar.TypeLink {
381380
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
382-
if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
381+
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
383382
return err
384383
}
385384
}
386385
} else if hdr.Typeflag != tar.TypeSymlink {
387-
if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
386+
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
388387
return err
389388
}
390389
} else {
390+
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
391391
if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
392392
return err
393393
}
@@ -644,8 +644,8 @@ loop:
644644

645645
for _, hdr := range dirs {
646646
path := filepath.Join(dest, hdr.Name)
647-
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
648-
if err := syscall.UtimesNano(path, ts); err != nil {
647+
648+
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
649649
return err
650650
}
651651
}

pkg/archive/diff.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"path/filepath"
1010
"runtime"
1111
"strings"
12-
"syscall"
1312

1413
"github.com/Sirupsen/logrus"
1514
"github.com/docker/docker/pkg/pools"
@@ -184,8 +183,7 @@ func UnpackLayer(dest string, layer Reader) (size int64, err error) {
184183

185184
for _, hdr := range dirs {
186185
path := filepath.Join(dest, hdr.Name)
187-
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
188-
if err := syscall.UtimesNano(path, ts); err != nil {
186+
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
189187
return 0, err
190188
}
191189
}

pkg/system/chtimes.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package system
2+
3+
import (
4+
"os"
5+
"time"
6+
)
7+
8+
// Chtimes changes the access time and modified time of a file at the given path
9+
func Chtimes(name string, atime time.Time, mtime time.Time) error {
10+
unixMinTime := time.Unix(0, 0)
11+
// The max Unix time is 33 bits set
12+
unixMaxTime := unixMinTime.Add((1<<33 - 1) * time.Second)
13+
14+
// If the modified time is prior to the Unix Epoch, or after the
15+
// end of Unix Time, os.Chtimes has undefined behavior
16+
// default to Unix Epoch in this case, just in case
17+
18+
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
19+
atime = unixMinTime
20+
}
21+
22+
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
23+
mtime = unixMinTime
24+
}
25+
26+
if err := os.Chtimes(name, atime, mtime); err != nil {
27+
return err
28+
}
29+
30+
return nil
31+
}

pkg/system/chtimes_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package system
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
"time"
9+
)
10+
11+
// prepareTempFile creates a temporary file in a temporary directory.
12+
func prepareTempFile(t *testing.T) (string, string) {
13+
dir, err := ioutil.TempDir("", "docker-system-test")
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
file := filepath.Join(dir, "exist")
19+
if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil {
20+
t.Fatal(err)
21+
}
22+
return file, dir
23+
}
24+
25+
// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent
26+
func TestChtimes(t *testing.T) {
27+
file, dir := prepareTempFile(t)
28+
defer os.RemoveAll(dir)
29+
30+
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
31+
unixEpochTime := time.Unix(0, 0)
32+
afterUnixEpochTime := time.Unix(100, 0)
33+
// The max Unix time is 33 bits set
34+
unixMaxTime := unixEpochTime.Add((1<<33 - 1) * time.Second)
35+
afterUnixMaxTime := unixMaxTime.Add(100 * time.Second)
36+
37+
// Test both aTime and mTime set to Unix Epoch
38+
Chtimes(file, unixEpochTime, unixEpochTime)
39+
40+
f, err := os.Stat(file)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
45+
if f.ModTime() != unixEpochTime {
46+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
47+
}
48+
49+
// Test aTime before Unix Epoch and mTime set to Unix Epoch
50+
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
51+
52+
f, err = os.Stat(file)
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
57+
if f.ModTime() != unixEpochTime {
58+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
59+
}
60+
61+
// Test aTime set to Unix Epoch and mTime before Unix Epoch
62+
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
63+
64+
f, err = os.Stat(file)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
if f.ModTime() != unixEpochTime {
70+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
71+
}
72+
73+
// Test both aTime and mTime set to after Unix Epoch (valid time)
74+
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
75+
76+
f, err = os.Stat(file)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
81+
if f.ModTime() != afterUnixEpochTime {
82+
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
83+
}
84+
85+
// Test both aTime and mTime set to Unix max time
86+
Chtimes(file, unixMaxTime, unixMaxTime)
87+
88+
f, err = os.Stat(file)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
93+
if f.ModTime() != unixMaxTime {
94+
t.Fatalf("Expected: %s, got: %s", unixMaxTime, f.ModTime())
95+
}
96+
97+
// Test aTime after Unix max time and mTime set to Unix max time
98+
Chtimes(file, afterUnixMaxTime, unixMaxTime)
99+
100+
f, err = os.Stat(file)
101+
if err != nil {
102+
t.Fatal(err)
103+
}
104+
105+
if f.ModTime() != unixMaxTime {
106+
t.Fatalf("Expected: %s, got: %s", unixMaxTime, f.ModTime())
107+
}
108+
109+
// Test aTime set to Unix Epoch and mTime before Unix Epoch
110+
Chtimes(file, unixMaxTime, afterUnixMaxTime)
111+
112+
f, err = os.Stat(file)
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
117+
if f.ModTime() != unixEpochTime {
118+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
119+
}
120+
}

pkg/system/chtimes_unix_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// +build linux freebsd
2+
3+
package system
4+
5+
import (
6+
"os"
7+
"syscall"
8+
"testing"
9+
"time"
10+
)
11+
12+
// TestChtimes tests Chtimes access time on a tempfile on Linux
13+
func TestChtimesLinux(t *testing.T) {
14+
file, dir := prepareTempFile(t)
15+
defer os.RemoveAll(dir)
16+
17+
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
18+
unixEpochTime := time.Unix(0, 0)
19+
afterUnixEpochTime := time.Unix(100, 0)
20+
// The max Unix time is 33 bits set
21+
unixMaxTime := unixEpochTime.Add((1<<33 - 1) * time.Second)
22+
afterUnixMaxTime := unixMaxTime.Add(100 * time.Second)
23+
24+
// Test both aTime and mTime set to Unix Epoch
25+
Chtimes(file, unixEpochTime, unixEpochTime)
26+
27+
f, err := os.Stat(file)
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
stat := f.Sys().(*syscall.Stat_t)
33+
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
34+
if aTime != unixEpochTime {
35+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
36+
}
37+
38+
// Test aTime before Unix Epoch and mTime set to Unix Epoch
39+
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
40+
41+
f, err = os.Stat(file)
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
stat = f.Sys().(*syscall.Stat_t)
47+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
48+
if aTime != unixEpochTime {
49+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
50+
}
51+
52+
// Test aTime set to Unix Epoch and mTime before Unix Epoch
53+
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
54+
55+
f, err = os.Stat(file)
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
60+
stat = f.Sys().(*syscall.Stat_t)
61+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
62+
if aTime != unixEpochTime {
63+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
64+
}
65+
66+
// Test both aTime and mTime set to after Unix Epoch (valid time)
67+
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
68+
69+
f, err = os.Stat(file)
70+
if err != nil {
71+
t.Fatal(err)
72+
}
73+
74+
stat = f.Sys().(*syscall.Stat_t)
75+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
76+
if aTime != afterUnixEpochTime {
77+
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
78+
}
79+
80+
// Test both aTime and mTime set to Unix max time
81+
Chtimes(file, unixMaxTime, unixMaxTime)
82+
83+
f, err = os.Stat(file)
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
88+
stat = f.Sys().(*syscall.Stat_t)
89+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
90+
if aTime != unixMaxTime {
91+
t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime)
92+
}
93+
94+
// Test aTime after Unix max time and mTime set to Unix max time
95+
Chtimes(file, afterUnixMaxTime, unixMaxTime)
96+
97+
f, err = os.Stat(file)
98+
if err != nil {
99+
t.Fatal(err)
100+
}
101+
102+
stat = f.Sys().(*syscall.Stat_t)
103+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
104+
if aTime != unixEpochTime {
105+
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
106+
}
107+
108+
// Test aTime set to Unix Epoch and mTime before Unix Epoch
109+
Chtimes(file, unixMaxTime, afterUnixMaxTime)
110+
111+
f, err = os.Stat(file)
112+
if err != nil {
113+
t.Fatal(err)
114+
}
115+
116+
stat = f.Sys().(*syscall.Stat_t)
117+
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
118+
if aTime != unixMaxTime {
119+
t.Fatalf("Expected: %s, got: %s", unixMaxTime, aTime)
120+
}
121+
}

0 commit comments

Comments
 (0)