1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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>
This commit is contained in:
Sargun Dhillon 2017-11-23 16:31:31 -08:00
parent 0eac562281
commit 77a2bc3e5b
2 changed files with 106 additions and 4 deletions

View file

@ -11,6 +11,7 @@ package copy
*/
import "C"
import (
"container/list"
"fmt"
"io"
"os"
@ -111,6 +112,11 @@ type fileID struct {
ino uint64
}
type dirMtimeInfo struct {
dstPath *string
stat *syscall.Stat_t
}
// DirCopy copies or hardlinks the contents of one directory to another,
// properly handling xattrs, and soft links
//
@ -118,9 +124,11 @@ type fileID struct {
func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
copyWithFileRange := true
copyWithFileClone := true
// This is a map of source file inodes to dst file paths
copiedFiles := make(map[fileID]string)
dirsToSetMtimes := list.New()
err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
if err != nil {
return err
@ -226,7 +234,9 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
// system.Chtimes doesn't support a NOFOLLOW flag atm
// nolint: unconvert
if !isSymlink {
if f.IsDir() {
dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat})
} else if !isSymlink {
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
@ -240,7 +250,18 @@ func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
}
return nil
})
return err
if err != nil {
return err
}
for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() {
mtimeInfo := e.Value.(*dirMtimeInfo)
ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim}
if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil {
return err
}
}
return nil
}
func doCopyXattrs(srcPath, dstPath string) error {

View file

@ -3,17 +3,20 @@
package copy
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"syscall"
"testing"
"golang.org/x/sys/unix"
"time"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestIsCopyFileRangeSyscallAvailable(t *testing.T) {
@ -47,6 +50,84 @@ func TestCopyWithoutRange(t *testing.T) {
doCopyTest(t, &copyWithFileRange, &copyWithFileClone)
}
func TestCopyDir(t *testing.T) {
srcDir, err := ioutil.TempDir("", "srcDir")
require.NoError(t, err)
populateSrcDir(t, srcDir, 3)
dstDir, err := ioutil.TempDir("", "testdst")
require.NoError(t, err)
defer os.RemoveAll(dstDir)
assert.NoError(t, DirCopy(srcDir, dstDir, Content, false))
require.NoError(t, filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
relPath, err := filepath.Rel(srcDir, srcPath)
require.NoError(t, err)
if relPath == "." {
return nil
}
dstPath := filepath.Join(dstDir, relPath)
require.NoError(t, err)
// If we add non-regular dirs and files to the test
// then we need to add more checks here.
dstFileInfo, err := os.Lstat(dstPath)
require.NoError(t, err)
srcFileSys := f.Sys().(*syscall.Stat_t)
dstFileSys := dstFileInfo.Sys().(*syscall.Stat_t)
t.Log(relPath)
if srcFileSys.Dev == dstFileSys.Dev {
assert.NotEqual(t, srcFileSys.Ino, dstFileSys.Ino)
}
// Todo: check size, and ctim is not equal
/// on filesystems that have granular ctimes
assert.Equal(t, srcFileSys.Mode, dstFileSys.Mode)
assert.Equal(t, srcFileSys.Uid, dstFileSys.Uid)
assert.Equal(t, srcFileSys.Gid, dstFileSys.Gid)
assert.Equal(t, srcFileSys.Mtim, dstFileSys.Mtim)
return nil
}))
}
func randomMode(baseMode int) os.FileMode {
for i := 0; i < 7; i++ {
baseMode = baseMode | (1&rand.Intn(2))<<uint(i)
}
return os.FileMode(baseMode)
}
func populateSrcDir(t *testing.T, srcDir string, remainingDepth int) {
if remainingDepth == 0 {
return
}
aTime := time.Unix(rand.Int63(), 0)
mTime := time.Unix(rand.Int63(), 0)
for i := 0; i < 10; i++ {
dirName := filepath.Join(srcDir, fmt.Sprintf("srcdir-%d", i))
// Owner all bits set
require.NoError(t, os.Mkdir(dirName, randomMode(0700)))
populateSrcDir(t, dirName, remainingDepth-1)
require.NoError(t, system.Chtimes(dirName, aTime, mTime))
}
for i := 0; i < 10; i++ {
fileName := filepath.Join(srcDir, fmt.Sprintf("srcfile-%d", i))
// Owner read bit set
require.NoError(t, ioutil.WriteFile(fileName, []byte{}, randomMode(0400)))
require.NoError(t, system.Chtimes(fileName, aTime, mTime))
}
}
func doCopyTest(t *testing.T, copyWithFileRange, copyWithFileClone *bool) {
dir, err := ioutil.TempDir("", "docker-copy-check")
require.NoError(t, err)