diff --git a/fs/copy_linux.go b/fs/copy_linux.go index 40e401797..efa924861 100644 --- a/fs/copy_linux.go +++ b/fs/copy_linux.go @@ -7,6 +7,7 @@ import ( "github.com/containerd/continuity/sysx" "github.com/pkg/errors" + "golang.org/x/sys/unix" ) func copyFileInfo(fi os.FileInfo, name string) error { @@ -21,7 +22,8 @@ func copyFileInfo(fi os.FileInfo, name string) error { } } - if err := syscall.UtimesNano(name, []syscall.Timespec{st.Atim, st.Mtim}); err != nil { + timespec := []unix.Timespec{unix.Timespec(st.Atim), unix.Timespec(st.Mtim)} + if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil { return errors.Wrapf(err, "failed to utime %s", name) } diff --git a/fs/copy_test.go b/fs/copy_test.go index 475e76fdf..2a51bf60e 100644 --- a/fs/copy_test.go +++ b/fs/copy_test.go @@ -32,6 +32,20 @@ func TestCopyDirectory(t *testing.T) { } } +// This test used to fail because link-no-nothing.txt would be copied first, +// then file operations in dst during the CopyDir would follow the symlink and +// fail. +func TestCopyDirectoryWithLocalSymlink(t *testing.T) { + apply := fstest.Apply( + fstest.CreateFile("nothing.txt", []byte{0x00, 0x00}, 0755), + fstest.Symlink("nothing.txt", "link-no-nothing.txt"), + ) + + if err := testCopy(apply); err != nil { + t.Fatalf("Copy test failed: %+v", err) + } +} + func testCopy(apply fstest.Applier) error { t1, err := ioutil.TempDir("", "test-copy-src-") if err != nil {