Skip to content

Commit 77a2bc3

Browse files
author
Sargun Dhillon
committed
Fix setting mtimes on directories
Previously, the code would set the mtime on the directories before creating files in the directory itself. This was problematic because it resulted in the mtimes on the directories being incorrectly set. This change makes it so that the mtime is set only _after_ all of the files have been created. Signed-off-by: Sargun Dhillon <sargun@sargun.me>
1 parent 0eac562 commit 77a2bc3

File tree

2 files changed

+106
-4
lines changed

2 files changed

+106
-4
lines changed

daemon/graphdriver/copy/copy.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package copy
1111
*/
1212
import "C"
1313
import (
14+
"container/list"
1415
"fmt"
1516
"io"
1617
"os"
@@ -111,16 +112,23 @@ type fileID struct {
111112
ino uint64
112113
}
113114

115+
type dirMtimeInfo struct {
116+
dstPath *string
117+
stat *syscall.Stat_t
118+
}
119+
114120
// DirCopy copies or hardlinks the contents of one directory to another,
115121
// properly handling xattrs, and soft links
116122
//
117123
// Copying xattrs can be opted out of by passing false for copyXattrs.
118124
func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
119125
copyWithFileRange := true
120126
copyWithFileClone := true
127+
121128
// This is a map of source file inodes to dst file paths
122129
copiedFiles := make(map[fileID]string)
123130

131+
dirsToSetMtimes := list.New()
124132
err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
125133
if err != nil {
126134
return err
@@ -226,7 +234,9 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
226234

227235
// system.Chtimes doesn't support a NOFOLLOW flag atm
228236
// nolint: unconvert
229-
if !isSymlink {
237+
if f.IsDir() {
238+
dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat})
239+
} else if !isSymlink {
230240
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
231241
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
232242
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
@@ -240,7 +250,18 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
240250
}
241251
return nil
242252
})
243-
return err
253+
if err != nil {
254+
return err
255+
}
256+
for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() {
257+
mtimeInfo := e.Value.(*dirMtimeInfo)
258+
ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim}
259+
if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil {
260+
return err
261+
}
262+
}
263+
264+
return nil
244265
}
245266

246267
func doCopyXattrs(srcPath, dstPath string) error {

daemon/graphdriver/copy/copy_test.go

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
package copy
44

55
import (
6+
"fmt"
67
"io/ioutil"
78
"math/rand"
89
"os"
910
"path/filepath"
11+
"syscall"
1012
"testing"
11-
12-
"golang.org/x/sys/unix"
13+
"time"
1314

1415
"github.com/docker/docker/pkg/parsers/kernel"
16+
"github.com/docker/docker/pkg/system"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/require"
19+
"golang.org/x/sys/unix"
1720
)
1821

1922
func TestIsCopyFileRangeSyscallAvailable(t *testing.T) {
@@ -47,6 +50,84 @@ func TestCopyWithoutRange(t *testing.T) {
4750
doCopyTest(t, &copyWithFileRange, &copyWithFileClone)
4851
}
4952

53+
func TestCopyDir(t *testing.T) {
54+
srcDir, err := ioutil.TempDir("", "srcDir")
55+
require.NoError(t, err)
56+
populateSrcDir(t, srcDir, 3)
57+
58+
dstDir, err := ioutil.TempDir("", "testdst")
59+
require.NoError(t, err)
60+
defer os.RemoveAll(dstDir)
61+
62+
assert.NoError(t, DirCopy(srcDir, dstDir, Content, false))
63+
require.NoError(t, filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
64+
if err != nil {
65+
return err
66+
}
67+
68+
// Rebase path
69+
relPath, err := filepath.Rel(srcDir, srcPath)
70+
require.NoError(t, err)
71+
if relPath == "." {
72+
return nil
73+
}
74+
75+
dstPath := filepath.Join(dstDir, relPath)
76+
require.NoError(t, err)
77+
78+
// If we add non-regular dirs and files to the test
79+
// then we need to add more checks here.
80+
dstFileInfo, err := os.Lstat(dstPath)
81+
require.NoError(t, err)
82+
83+
srcFileSys := f.Sys().(*syscall.Stat_t)
84+
dstFileSys := dstFileInfo.Sys().(*syscall.Stat_t)
85+
86+
t.Log(relPath)
87+
if srcFileSys.Dev == dstFileSys.Dev {
88+
assert.NotEqual(t, srcFileSys.Ino, dstFileSys.Ino)
89+
}
90+
// Todo: check size, and ctim is not equal
91+
/// on filesystems that have granular ctimes
92+
assert.Equal(t, srcFileSys.Mode, dstFileSys.Mode)
93+
assert.Equal(t, srcFileSys.Uid, dstFileSys.Uid)
94+
assert.Equal(t, srcFileSys.Gid, dstFileSys.Gid)
95+
assert.Equal(t, srcFileSys.Mtim, dstFileSys.Mtim)
96+
97+
return nil
98+
}))
99+
}
100+
101+
func randomMode(baseMode int) os.FileMode {
102+
for i := 0; i < 7; i++ {
103+
baseMode = baseMode | (1&rand.Intn(2))<<uint(i)
104+
}
105+
return os.FileMode(baseMode)
106+
}
107+
108+
func populateSrcDir(t *testing.T, srcDir string, remainingDepth int) {
109+
if remainingDepth == 0 {
110+
return
111+
}
112+
aTime := time.Unix(rand.Int63(), 0)
113+
mTime := time.Unix(rand.Int63(), 0)
114+
115+
for i := 0; i < 10; i++ {
116+
dirName := filepath.Join(srcDir, fmt.Sprintf("srcdir-%d", i))
117+
// Owner all bits set
118+
require.NoError(t, os.Mkdir(dirName, randomMode(0700)))
119+
populateSrcDir(t, dirName, remainingDepth-1)
120+
require.NoError(t, system.Chtimes(dirName, aTime, mTime))
121+
}
122+
123+
for i := 0; i < 10; i++ {
124+
fileName := filepath.Join(srcDir, fmt.Sprintf("srcfile-%d", i))
125+
// Owner read bit set
126+
require.NoError(t, ioutil.WriteFile(fileName, []byte{}, randomMode(0400)))
127+
require.NoError(t, system.Chtimes(fileName, aTime, mTime))
128+
}
129+
}
130+
50131
func doCopyTest(t *testing.T, copyWithFileRange, copyWithFileClone *bool) {
51132
dir, err := ioutil.TempDir("", "docker-copy-check")
52133
require.NoError(t, err)

0 commit comments

Comments
 (0)