From 8ae2264e964b4be38b252398b5f013d6ae23fcde Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 16 Jul 2022 14:23:37 +0200 Subject: [PATCH 01/10] Support preserving symlink times under unix --- copy.go | 13 ++++++++++++- go.mod | 7 +++++-- go.sum | 2 ++ preserve_ltimes.go | 11 +++++++++++ preserve_ltimes_x.go | 22 ++++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 preserve_ltimes.go create mode 100644 preserve_ltimes_x.go diff --git a/copy.go b/copy.go index 81e1ecc..a84b759 100644 --- a/copy.go +++ b/copy.go @@ -171,7 +171,18 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { func onsymlink(src, dest string, opt Options) error { switch opt.OnSymlink(src) { case Shallow: - return lcopy(src, dest) + err := lcopy(src, dest) + if err != nil { + return err + } + if opt.PreserveTimes { + info, err := os.Stat(src) + if err != nil { + return err + } + return preserveLtimes(info, dest) + } + return nil case Deep: orig, err := os.Readlink(src) if err != nil { diff --git a/go.mod b/go.mod index 703f47f..11d1354 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ -module github.com/otiai10/copy +module github.com/fako1024/copy go 1.14 -require github.com/otiai10/mint v1.3.3 +require ( + github.com/otiai10/mint v1.3.3 + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/go.sum b/go.sum index 152c77b..a632948 100644 --- a/go.sum +++ b/go.sum @@ -4,3 +4,5 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/preserve_ltimes.go b/preserve_ltimes.go new file mode 100644 index 0000000..f65a4fa --- /dev/null +++ b/preserve_ltimes.go @@ -0,0 +1,11 @@ +//go:build windows || js +// +build windows js + +package copy + +import "os" + +func preserveLtimes(srcinfo os.FileInfo, dest string) error { + //Unsupported + return nil +} diff --git a/preserve_ltimes_x.go b/preserve_ltimes_x.go new file mode 100644 index 0000000..84b5662 --- /dev/null +++ b/preserve_ltimes_x.go @@ -0,0 +1,22 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package copy + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func preserveLtimes(srcinfo os.FileInfo, dest string) error { + spec := getTimeSpec(srcinfo) + + if err := unix.Lutimes(dest, []unix.Timeval{ + {Sec: spec.Atime.Unix(), Usec: spec.Atime.UnixNano() / 1000 % 1000}, + {Sec: spec.Mtime.Unix(), Usec: spec.Mtime.UnixNano() / 1000 % 1000}, + }); err != nil { + return err + } + return nil +} From e008e36785a6915076f869e526c015b25e3700d3 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 16 Jul 2022 14:49:21 +0200 Subject: [PATCH 02/10] Fix compilation on non-64bit architectures and file nomenclature --- preserve_ltimes.go | 19 +++++++++++++++---- preserve_ltimes_x.go | 19 ++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/preserve_ltimes.go b/preserve_ltimes.go index f65a4fa..03a20f6 100644 --- a/preserve_ltimes.go +++ b/preserve_ltimes.go @@ -1,11 +1,22 @@ -//go:build windows || js -// +build windows js +//go:build !windows && !plan9 && !js +// +build !windows,!plan9,!js package copy -import "os" +import ( + "os" + + "golang.org/x/sys/unix" +) func preserveLtimes(srcinfo os.FileInfo, dest string) error { - //Unsupported + spec := getTimeSpec(srcinfo) + + if err := unix.Lutimes(dest, []unix.Timeval{ + unix.NsecToTimeval(spec.Atime.UnixNano()), + unix.NsecToTimeval(spec.Mtime.UnixNano()), + }); err != nil { + return err + } return nil } diff --git a/preserve_ltimes_x.go b/preserve_ltimes_x.go index 84b5662..10e521a 100644 --- a/preserve_ltimes_x.go +++ b/preserve_ltimes_x.go @@ -1,22 +1,11 @@ -//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris +//go:build windows || js || plan9 +// +build windows js plan9 package copy -import ( - "os" - - "golang.org/x/sys/unix" -) +import "os" func preserveLtimes(srcinfo os.FileInfo, dest string) error { - spec := getTimeSpec(srcinfo) - - if err := unix.Lutimes(dest, []unix.Timeval{ - {Sec: spec.Atime.Unix(), Usec: spec.Atime.UnixNano() / 1000 % 1000}, - {Sec: spec.Mtime.Unix(), Usec: spec.Mtime.UnixNano() / 1000 % 1000}, - }); err != nil { - return err - } + //Unsupported return nil } From d15fed525155b56ec211d2e8a6e33f4d7f5f0979 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 16 Jul 2022 15:05:47 +0200 Subject: [PATCH 03/10] Add test for symlink timestamp preservation and fix symlink stat() call --- all_test.go | 9 +++++++++ copy.go | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/all_test.go b/all_test.go index b77a182..8ce085d 100644 --- a/all_test.go +++ b/all_test.go @@ -249,6 +249,15 @@ func TestOptions_PreserveTimes(t *testing.T) { Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) } + + orig, err := os.Lstat("test/data/case09/symlink") + Expect(t, err).ToBe(nil) + plain, err := os.Lstat("test/data.copy/case09/symlink") + Expect(t, err).ToBe(nil) + preserved, err := os.Lstat("test/data.copy/case09-preservetimes/symlink") + Expect(t, err).ToBe(nil) + Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) + Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) } func TestOptions_OnDirExists(t *testing.T) { diff --git a/copy.go b/copy.go index a84b759..253f2f1 100644 --- a/copy.go +++ b/copy.go @@ -176,7 +176,7 @@ func onsymlink(src, dest string, opt Options) error { return err } if opt.PreserveTimes { - info, err := os.Stat(src) + info, err := os.Lstat(src) if err != nil { return err } From d6c84c407de8260ef9f987a9c2b3c39513cc616c Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 16 Jul 2022 15:12:42 +0200 Subject: [PATCH 04/10] Fix go.mod prior to PR --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 11d1354..2abf99c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/fako1024/copy +module github.com/otiai10/copy go 1.14 From 43eff8e7b84238df8556052664cde824ad3127b3 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 16 Jul 2022 15:20:03 +0200 Subject: [PATCH 05/10] Increase code simplicity --- preserve_ltimes.go | 7 ++----- preserve_ltimes_x.go | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/preserve_ltimes.go b/preserve_ltimes.go index 03a20f6..ffc5944 100644 --- a/preserve_ltimes.go +++ b/preserve_ltimes.go @@ -12,11 +12,8 @@ import ( func preserveLtimes(srcinfo os.FileInfo, dest string) error { spec := getTimeSpec(srcinfo) - if err := unix.Lutimes(dest, []unix.Timeval{ + return unix.Lutimes(dest, []unix.Timeval{ unix.NsecToTimeval(spec.Atime.UnixNano()), unix.NsecToTimeval(spec.Mtime.UnixNano()), - }); err != nil { - return err - } - return nil + }) } diff --git a/preserve_ltimes_x.go b/preserve_ltimes_x.go index 10e521a..0acb621 100644 --- a/preserve_ltimes_x.go +++ b/preserve_ltimes_x.go @@ -6,6 +6,5 @@ package copy import "os" func preserveLtimes(srcinfo os.FileInfo, dest string) error { - //Unsupported - return nil + return nil // Unsupported } From 3cf1610ca3a5b6fa9544d74c3d2e0bd1ea9303e4 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 23 Jul 2022 10:50:36 +0200 Subject: [PATCH 06/10] Adapt tests to be architecture specific --- all_test.go | 9 --------- go.mod | 2 +- preserve_ltimes_test.go | 28 ++++++++++++++++++++++++++++ preserve_ltimes_x_test.go | 28 ++++++++++++++++++++++++++++ test/data/case15/README.md | 5 +++++ test/data/case15/symlink | 1 + 6 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 preserve_ltimes_test.go create mode 100644 preserve_ltimes_x_test.go create mode 100644 test/data/case15/README.md create mode 120000 test/data/case15/symlink diff --git a/all_test.go b/all_test.go index 8ce085d..b77a182 100644 --- a/all_test.go +++ b/all_test.go @@ -249,15 +249,6 @@ func TestOptions_PreserveTimes(t *testing.T) { Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) } - - orig, err := os.Lstat("test/data/case09/symlink") - Expect(t, err).ToBe(nil) - plain, err := os.Lstat("test/data.copy/case09/symlink") - Expect(t, err).ToBe(nil) - preserved, err := os.Lstat("test/data.copy/case09-preservetimes/symlink") - Expect(t, err).ToBe(nil) - Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) - Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) } func TestOptions_OnDirExists(t *testing.T) { diff --git a/go.mod b/go.mod index 2abf99c..11d1354 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/otiai10/copy +module github.com/fako1024/copy go 1.14 diff --git a/preserve_ltimes_test.go b/preserve_ltimes_test.go new file mode 100644 index 0000000..58b3407 --- /dev/null +++ b/preserve_ltimes_test.go @@ -0,0 +1,28 @@ +//go:build !windows && !plan9 && !js +// +build !windows,!plan9,!js + +package copy + +import ( + "os" + "testing" + + . "github.com/otiai10/mint" +) + +func TestOptions_PreserveLTimes(t *testing.T) { + err := Copy("test/data/case15", "test/data.copy/case15") + Expect(t, err).ToBe(nil) + opt := Options{PreserveTimes: true} + err = Copy("test/data/case15", "test/data.copy/case15-preserveltimes", opt) + Expect(t, err).ToBe(nil) + + orig, err := os.Lstat("test/data/case15/symlink") + Expect(t, err).ToBe(nil) + plain, err := os.Lstat("test/data.copy/case15/symlink") + Expect(t, err).ToBe(nil) + preserved, err := os.Lstat("test/data.copy/case15-preserveltimes/symlink") + Expect(t, err).ToBe(nil) + Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) + Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) +} diff --git a/preserve_ltimes_x_test.go b/preserve_ltimes_x_test.go new file mode 100644 index 0000000..1c73b5a --- /dev/null +++ b/preserve_ltimes_x_test.go @@ -0,0 +1,28 @@ +//go:build windows || js || plan9 +// +build windows js plan9 + +package copy + +import ( + "os" + "testing" + + . "github.com/otiai10/mint" +) + +func TestOptions_PreserveLTimes(t *testing.T) { + err := Copy("test/data/case15", "test/data.copy/case15") + Expect(t, err).ToBe(nil) + opt := Options{PreserveTimes: true} + err = Copy("test/data/case15", "test/data.copy/case15-preserveltimes", opt) + Expect(t, err).ToBe(nil) + + orig, err := os.Lstat("test/data/case15/symlink") + Expect(t, err).ToBe(nil) + plain, err := os.Lstat("test/data.copy/case15/symlink") + Expect(t, err).ToBe(nil) + preserved, err := os.Lstat("test/data.copy/case15-preserveltimes/symlink") + Expect(t, err).ToBe(nil) + Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) + Expect(t, preserved.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) +} diff --git a/test/data/case15/README.md b/test/data/case15/README.md new file mode 100644 index 0000000..ba38bad --- /dev/null +++ b/test/data/case15/README.md @@ -0,0 +1,5 @@ +File with specific atime and mtime. + +These two properties should be preserved if we provide the PreserveTimes options to copy.Copy. + +The properties should be preserverd also for the directories and the links. diff --git a/test/data/case15/symlink b/test/data/case15/symlink new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/test/data/case15/symlink @@ -0,0 +1 @@ +README.md \ No newline at end of file From 8aae6f8b2da94f8bdac42c95fc44b9e1fa941d65 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Sat, 23 Jul 2022 11:08:33 +0200 Subject: [PATCH 07/10] Restore go.mod to allow for merge in PR --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 11d1354..2abf99c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/fako1024/copy +module github.com/otiai10/copy go 1.14 From bc19925a2d3417a4faf129b0398f84f0717288f7 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Wed, 3 Aug 2022 21:09:36 +0200 Subject: [PATCH 08/10] Make Lstat() call architecture-specific and move into function --- copy.go | 6 +----- preserve_ltimes.go | 13 +++++++------ preserve_ltimes_test.go | 7 +++++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/copy.go b/copy.go index 253f2f1..1a7c126 100644 --- a/copy.go +++ b/copy.go @@ -176,11 +176,7 @@ func onsymlink(src, dest string, opt Options) error { return err } if opt.PreserveTimes { - info, err := os.Lstat(src) - if err != nil { - return err - } - return preserveLtimes(info, dest) + return preserveLtimes(src, dest) } return nil case Deep: diff --git a/preserve_ltimes.go b/preserve_ltimes.go index ffc5944..cc006d3 100644 --- a/preserve_ltimes.go +++ b/preserve_ltimes.go @@ -4,16 +4,17 @@ package copy import ( - "os" - "golang.org/x/sys/unix" ) -func preserveLtimes(srcinfo os.FileInfo, dest string) error { - spec := getTimeSpec(srcinfo) +func preserveLtimes(src, dest string) error { + info := new(unix.Stat_t) + if err := unix.Lstat(src, info); err != nil { + return err + } return unix.Lutimes(dest, []unix.Timeval{ - unix.NsecToTimeval(spec.Atime.UnixNano()), - unix.NsecToTimeval(spec.Mtime.UnixNano()), + unix.NsecToTimeval(info.Atim.Nano()), + unix.NsecToTimeval(info.Mtim.Nano()), }) } diff --git a/preserve_ltimes_test.go b/preserve_ltimes_test.go index 58b3407..4289776 100644 --- a/preserve_ltimes_test.go +++ b/preserve_ltimes_test.go @@ -8,6 +8,7 @@ import ( "testing" . "github.com/otiai10/mint" + "golang.org/x/sys/unix" ) func TestOptions_PreserveLTimes(t *testing.T) { @@ -26,3 +27,9 @@ func TestOptions_PreserveLTimes(t *testing.T) { Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix()) Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix()) } + +func TestOptions_PreserveLTimesErrorReturn(t *testing.T) { + err := preserveLtimes("doesnotexist_original.txt", "doesnotexist_copy.txt") + Expect(t, err).ToBe(unix.ENOENT) + Expect(t, os.IsNotExist(err)).ToBe(true) +} From e8d6646bea0411854a8455d34eec60dda6f9253e Mon Sep 17 00:00:00 2001 From: fako1024 Date: Tue, 9 Aug 2022 17:38:18 +0200 Subject: [PATCH 09/10] Amend PR feedback and fix fallback scenario --- copy.go | 3 +-- go.mod | 2 +- preserve_ltimes_x.go | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/copy.go b/copy.go index 1a7c126..77eaf3c 100644 --- a/copy.go +++ b/copy.go @@ -171,8 +171,7 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) { func onsymlink(src, dest string, opt Options) error { switch opt.OnSymlink(src) { case Shallow: - err := lcopy(src, dest) - if err != nil { + if err := lcopy(src, dest); err != nil { return err } if opt.PreserveTimes { diff --git a/go.mod b/go.mod index 2abf99c..11d1354 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/otiai10/copy +module github.com/fako1024/copy go 1.14 diff --git a/preserve_ltimes_x.go b/preserve_ltimes_x.go index 0acb621..02aec40 100644 --- a/preserve_ltimes_x.go +++ b/preserve_ltimes_x.go @@ -3,8 +3,6 @@ package copy -import "os" - -func preserveLtimes(srcinfo os.FileInfo, dest string) error { +func preserveLtimes(src, dest string) error { return nil // Unsupported } From 84f883f63c6f81cd24b2e4ac8c5a50b2ba8bead3 Mon Sep 17 00:00:00 2001 From: fako1024 Date: Tue, 9 Aug 2022 17:43:05 +0200 Subject: [PATCH 10/10] Restore go.mod to allow for merge in PR --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 11d1354..2abf99c 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/fako1024/copy +module github.com/otiai10/copy go 1.14