| //go:build linux | |
| package copy // import "github.com/docker/docker/daemon/graphdriver/copy" | |
| import ( | |
| "fmt" | |
| "math/rand" | |
| "os" | |
| "path/filepath" | |
| "syscall" | |
| "testing" | |
| "time" | |
| "github.com/docker/docker/pkg/system" | |
| "golang.org/x/sys/unix" | |
| "gotest.tools/v3/assert" | |
| is "gotest.tools/v3/assert/cmp" | |
| ) | |
| func TestCopy(t *testing.T) { | |
| copyWithFileRange := true | |
| copyWithFileClone := true | |
| doCopyTest(t, ©WithFileRange, ©WithFileClone) | |
| } | |
| func TestCopyWithoutRange(t *testing.T) { | |
| copyWithFileRange := false | |
| copyWithFileClone := false | |
| doCopyTest(t, ©WithFileRange, ©WithFileClone) | |
| } | |
| func TestCopyDir(t *testing.T) { | |
| srcDir, err := os.MkdirTemp("", "srcDir") | |
| assert.NilError(t, err) | |
| defer os.RemoveAll(srcDir) | |
| populateSrcDir(t, srcDir, 3) | |
| dstDir, err := os.MkdirTemp("", "testdst") | |
| assert.NilError(t, err) | |
| defer os.RemoveAll(dstDir) | |
| assert.Check(t, DirCopy(srcDir, dstDir, Content, false)) | |
| assert.NilError(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) | |
| assert.NilError(t, err) | |
| if relPath == "." { | |
| return nil | |
| } | |
| dstPath := filepath.Join(dstDir, relPath) | |
| // If we add non-regular dirs and files to the test | |
| // then we need to add more checks here. | |
| dstFileInfo, err := os.Lstat(dstPath) | |
| assert.NilError(t, err) | |
| srcFileSys := f.Sys().(*syscall.Stat_t) | |
| dstFileSys := dstFileInfo.Sys().(*syscall.Stat_t) | |
| t.Log(relPath) | |
| if srcFileSys.Dev == dstFileSys.Dev { | |
| assert.Check(t, srcFileSys.Ino != dstFileSys.Ino) | |
| } | |
| // Todo: check size, and ctim is not equal on filesystems that have granular ctimes | |
| assert.Check(t, is.DeepEqual(srcFileSys.Mode, dstFileSys.Mode)) | |
| assert.Check(t, is.DeepEqual(srcFileSys.Uid, dstFileSys.Uid)) | |
| assert.Check(t, is.DeepEqual(srcFileSys.Gid, dstFileSys.Gid)) | |
| assert.Check(t, is.DeepEqual(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 | |
| assert.NilError(t, os.Mkdir(dirName, randomMode(0o700))) | |
| populateSrcDir(t, dirName, remainingDepth-1) | |
| assert.NilError(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 | |
| assert.NilError(t, os.WriteFile(fileName, []byte{}, randomMode(0o400))) | |
| assert.NilError(t, system.Chtimes(fileName, aTime, mTime)) | |
| } | |
| } | |
| func doCopyTest(t *testing.T, copyWithFileRange, copyWithFileClone *bool) { | |
| dir, err := os.MkdirTemp("", "docker-copy-check") | |
| assert.NilError(t, err) | |
| defer os.RemoveAll(dir) | |
| srcFilename := filepath.Join(dir, "srcFilename") | |
| dstFilename := filepath.Join(dir, "dstFilename") | |
| r := rand.New(rand.NewSource(0)) | |
| buf := make([]byte, 1024) | |
| _, err = r.Read(buf) | |
| assert.NilError(t, err) | |
| assert.NilError(t, os.WriteFile(srcFilename, buf, 0o777)) | |
| fileinfo, err := os.Stat(srcFilename) | |
| assert.NilError(t, err) | |
| assert.NilError(t, copyRegular(srcFilename, dstFilename, fileinfo, copyWithFileRange, copyWithFileClone)) | |
| readBuf, err := os.ReadFile(dstFilename) | |
| assert.NilError(t, err) | |
| assert.Check(t, is.DeepEqual(buf, readBuf)) | |
| } | |
| func TestCopyHardlink(t *testing.T) { | |
| var srcFile1FileInfo, srcFile2FileInfo, dstFile1FileInfo, dstFile2FileInfo unix.Stat_t | |
| srcDir, err := os.MkdirTemp("", "srcDir") | |
| assert.NilError(t, err) | |
| defer os.RemoveAll(srcDir) | |
| dstDir, err := os.MkdirTemp("", "dstDir") | |
| assert.NilError(t, err) | |
| defer os.RemoveAll(dstDir) | |
| srcFile1 := filepath.Join(srcDir, "file1") | |
| srcFile2 := filepath.Join(srcDir, "file2") | |
| dstFile1 := filepath.Join(dstDir, "file1") | |
| dstFile2 := filepath.Join(dstDir, "file2") | |
| assert.NilError(t, os.WriteFile(srcFile1, []byte{}, 0o777)) | |
| assert.NilError(t, os.Link(srcFile1, srcFile2)) | |
| assert.Check(t, DirCopy(srcDir, dstDir, Content, false)) | |
| assert.NilError(t, unix.Stat(srcFile1, &srcFile1FileInfo)) | |
| assert.NilError(t, unix.Stat(srcFile2, &srcFile2FileInfo)) | |
| assert.Equal(t, srcFile1FileInfo.Ino, srcFile2FileInfo.Ino) | |
| assert.NilError(t, unix.Stat(dstFile1, &dstFile1FileInfo)) | |
| assert.NilError(t, unix.Stat(dstFile2, &dstFile2FileInfo)) | |
| assert.Check(t, is.Equal(dstFile1FileInfo.Ino, dstFile2FileInfo.Ino)) | |
| } |