Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix symlinks #56

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 58 additions & 19 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
Expand All @@ -19,7 +20,8 @@ func TestMain(m *testing.M) {
}

func teardown(m *testing.M) {
os.RemoveAll("test/data/case03/case01")
os.RemoveAll("test/data/case03/relative")
os.RemoveAll("test/data/case03/absolute")
os.RemoveAll("test/data.copy")
os.RemoveAll("test/data.copyTime")
}
Expand All @@ -45,7 +47,7 @@ func TestCopy(t *testing.T) {
When(t, "source directory includes symbolic link", func(t *testing.T) {
err := Copy("test/data/case03", "test/data.copy/case03")
Expect(t, err).ToBe(nil)
info, err := os.Lstat("test/data.copy/case03/case01")
info, err := os.Lstat("test/data.copy/case03/relative")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(0)
})
Expand Down Expand Up @@ -113,19 +115,46 @@ func TestCopy_NamedPipe(t *testing.T) {
}

func TestOptions_OnSymlink(t *testing.T) {
originalCase01, err := os.Stat("test/data/case01")
Expect(t, err).ToBe(nil)
copyCase01, err := os.Stat("test/data.copy/case01")
Expect(t, err).ToBe(nil)
opt := Options{OnSymlink: func(string) SymlinkAction { return Deep }}
err := Copy("test/data/case03", "test/data.copy/case03.deep", opt)
err = Copy("test/data/case03", "test/data.copy/case03.deep/arbitrarily/deep", opt)
Expect(t, err).ToBe(nil)
info, err := os.Lstat("test/data.copy/case03.deep/arbitrarily/deep/relative")
Expect(t, err).ToBe(nil)
info, err := os.Lstat("test/data.copy/case03.deep/case01")
Expect(t, info.Mode()&os.ModeSymlink != 0).ToBe(false)
Expect(t, os.SameFile(originalCase01, info)).ToBe(false)
info, err = os.Lstat("test/data.copy/case03.deep/arbitrarily/deep/absolute")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).ToBe(os.FileMode(0))
Expect(t, info.Mode()&os.ModeSymlink != 0).ToBe(false)
Expect(t, os.SameFile(originalCase01, info)).ToBe(false)

opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }}
err = Copy("test/data/case03", "test/data.copy/case03.nested/arbitrarily/deep", opt)
// errors because relative symlinks are broken
Expect(t, errors.Is(err, os.ErrNotExist)).ToBe(true)

opt = Options{OnSymlink: func(string) SymlinkAction { return Shallow }}
err = Copy("test/data/case03", "test/data.copy/case03.shallow", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("test/data.copy/case03.shallow/case01")

// assert on the symlinks
info, err = os.Lstat("test/data.copy/case03.shallow/relative")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink != 0).ToBe(true)
info, err = os.Lstat("test/data.copy/case03.shallow/absolute")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink != 0).ToBe(true)

// compare the resolved symlinks
info, err = os.Stat("test/data.copy/case03.shallow/relative")
Expect(t, err).ToBe(nil)
Expect(t, os.SameFile(copyCase01, info)).ToBe(true)
info, err = os.Stat("test/data.copy/case03.shallow/absolute")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
Expect(t, os.SameFile(originalCase01, info)).ToBe(true)

opt = Options{OnSymlink: func(string) SymlinkAction { return Skip }}
err = Copy("test/data/case03", "test/data.copy/case03.skip", opt)
Expand All @@ -135,16 +164,26 @@ func TestOptions_OnSymlink(t *testing.T) {

err = Copy("test/data/case03", "test/data.copy/case03.default")
Expect(t, err).ToBe(nil)
info, err = os.Lstat("test/data.copy/case03.default/case01")
err = Copy("test/data/case03", "test/data.copy/case03.not-specified", Options{OnSymlink: nil})
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))

opt = Options{OnSymlink: nil}
err = Copy("test/data/case03", "test/data.copy/case03.not-specified", opt)
Expect(t, err).ToBe(nil)
info, err = os.Lstat("test/data.copy/case03.not-specified/case01")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink).Not().ToBe(os.FileMode(0))
for _, dir := range []string{"case03.default", "case03.not-specified"} {
info, err = os.Lstat("test/data.copy/" + dir + "/relative")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink == 0).ToBe(false)
info, err = os.Lstat("test/data.copy/" + dir + "/absolute")
Expect(t, err).ToBe(nil)
Expect(t, info.Mode()&os.ModeSymlink == 0).ToBe(false)

dest, err := os.Readlink("test/data.copy/" + dir + "/relative")
Expect(t, err).ToBe(nil)
Expect(t, dest).ToBe("../case01")
cwd, err := os.Getwd()
Expect(t, err).ToBe(nil)
dest, err = os.Readlink("test/data.copy/" + dir + "/absolute")
Expect(t, err).ToBe(nil)
Expect(t, dest).ToBe(filepath.Join(cwd, "test/data/case01"))
}
}

func TestOptions_Skip(t *testing.T) {
Expand Down Expand Up @@ -230,12 +269,12 @@ func TestOptions_PreserveTimes(t *testing.T) {
err = Copy("test/data/case09", "test/data.copy/case09-preservetimes", opt)
Expect(t, err).ToBe(nil)

for _, entry := range []string{"", "README.md", "symlink"} {
orig, err := os.Stat("test/data/case09/" + entry)
for _, entry := range []string{"", "README.md"} {
orig, err := os.Lstat("test/data/case09/" + entry)
Expect(t, err).ToBe(nil)
plain, err := os.Stat("test/data.copy/case09/" + entry)
plain, err := os.Lstat("test/data.copy/case09/" + entry)
Expect(t, err).ToBe(nil)
preserved, err := os.Stat("test/data.copy/case09-preservetimes/" + entry)
preserved, err := os.Lstat("test/data.copy/case09-preservetimes/" + entry)
Expect(t, err).ToBe(nil)
Expect(t, plain.ModTime().Unix()).Not().ToBe(orig.ModTime().Unix())
Expect(t, preserved.ModTime().Unix()).ToBe(orig.ModTime().Unix())
Expand Down
17 changes: 12 additions & 5 deletions copy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package copy

import (
"fmt"
"io"
"io/ioutil"
"os"
Expand Down Expand Up @@ -39,7 +40,7 @@ func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {
case info.IsDir():
err = dcopy(src, dest, info, opt)
case info.Mode()&os.ModeNamedPipe != 0:
err = pcopy(dest,info)
err = pcopy(dest, info)
default:
err = fcopy(src, dest, info, opt)
}
Expand Down Expand Up @@ -151,16 +152,16 @@ func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {
return
}

func onsymlink(src, dest string, info os.FileInfo, opt Options) error {
func onsymlink(src, dest string, _ os.FileInfo, opt Options) error {
switch opt.OnSymlink(src) {
case Shallow:
return lcopy(src, dest)
case Deep:
orig, err := os.Readlink(src)
orig, err := filepath.EvalSymlinks(src)
if err != nil {
return err
}
info, err = os.Lstat(orig)
info, err := os.Lstat(orig)
if err != nil {
return err
}
Expand All @@ -179,7 +180,13 @@ func lcopy(src, dest string) error {
if err != nil {
return err
}
return os.Symlink(src, dest)
if err := os.Symlink(src, dest); err != nil {
return err
}
if _, err = os.Stat(dest); err != nil {
return fmt.Errorf("symlink does not resolve: %w", err)
}
return nil
}

// fclose ANYHOW closes file,
Expand Down
10 changes: 9 additions & 1 deletion test_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
package copy

import (
"log"
"os"
"path/filepath"
"syscall"
"testing"
)

func setup(m *testing.M) {
os.MkdirAll("test/data.copy", os.ModePerm)
os.Symlink("test/data/case01", "test/data/case03/case01")
os.Symlink("../case01", "test/data/case03/relative")
os.Symlink("../case03", "test/data/case03/relative")
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("failed to get cwd: %v", err)
}
os.Symlink(filepath.Join(cwd, "test/data/case01"), "test/data/case03/absolute")
os.Chmod("test/data/case07/dir_0555", 0555)
os.Chmod("test/data/case07/file_0444", 0444)
syscall.Mkfifo("test/data/case11/foo/bar", 0555)
Expand Down