Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 37fe793

Browse files
authored
Merge pull request #657 from ibrasho-forks/fix-symlink-copying
internal/fs: handle symlinks in copyFile()
2 parents 07b4de8 + 4d224bd commit 37fe793

File tree

2 files changed

+184
-1
lines changed

2 files changed

+184
-1
lines changed

internal/fs/fs.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,13 @@ func CopyDir(src, dst string) error {
248248
// of the source file. The file mode will be copied from the source and
249249
// the copied data is synced/flushed to stable storage.
250250
func copyFile(src, dst string) (err error) {
251+
if sym, err := IsSymlink(src); err != nil {
252+
return err
253+
} else if sym {
254+
err := copySymlink(src, dst)
255+
return err
256+
}
257+
251258
in, err := os.Open(src)
252259
if err != nil {
253260
return
@@ -286,6 +293,22 @@ func copyFile(src, dst string) (err error) {
286293
return
287294
}
288295

296+
// copySymlink will resolve the src symlink and create a new symlink in dst.
297+
// If src is a relative symlink, dst will also be a relative symlink.
298+
func copySymlink(src, dst string) error {
299+
resolved, err := os.Readlink(src)
300+
if err != nil {
301+
return errors.Wrap(err, "failed to resolve symlink")
302+
}
303+
304+
err = os.Symlink(resolved, dst)
305+
if err != nil {
306+
return errors.Wrapf(err, "failed to create symlink %s to %s", src, resolved)
307+
}
308+
309+
return nil
310+
}
311+
289312
// IsDir determines is the path given is a directory or not.
290313
func IsDir(name string) (bool, error) {
291314
// TODO: lstat?
@@ -343,3 +366,13 @@ func IsRegular(name string) (bool, error) {
343366
}
344367
return true, nil
345368
}
369+
370+
// IsSymlink determines if the given path is a symbolic link.
371+
func IsSymlink(path string) (bool, error) {
372+
l, err := os.Lstat(path)
373+
if err != nil {
374+
return false, err
375+
}
376+
377+
return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil
378+
}

internal/fs/fs_test.go

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,75 @@ func TestCopyFile(t *testing.T) {
461461
}
462462
}
463463

464+
func TestCopyFileSymlink(t *testing.T) {
465+
dir, err := ioutil.TempDir("", "dep")
466+
if err != nil {
467+
t.Fatal(err)
468+
}
469+
defer os.RemoveAll(dir)
470+
471+
srcPath := filepath.Join(dir, "src")
472+
symlinkPath := filepath.Join(dir, "symlink")
473+
dstPath := filepath.Join(dir, "dst")
474+
475+
srcf, err := os.Create(srcPath)
476+
if err != nil {
477+
t.Fatal(err)
478+
}
479+
srcf.Close()
480+
481+
if err = os.Symlink(srcPath, symlinkPath); err != nil {
482+
t.Fatalf("could not create symlink: %s", err)
483+
}
484+
485+
if err = copyFile(symlinkPath, dstPath); err != nil {
486+
t.Fatalf("failed to copy symlink: %s", err)
487+
}
488+
489+
resolvedPath, err := os.Readlink(dstPath)
490+
if err != nil {
491+
t.Fatalf("could not resolve symlink: %s", err)
492+
}
493+
494+
if resolvedPath != srcPath {
495+
t.Fatalf("resolved path is incorrect. expected %s, got %s", srcPath, resolvedPath)
496+
}
497+
}
498+
499+
func TestCopyFileSymlinkToDirectory(t *testing.T) {
500+
dir, err := ioutil.TempDir("", "dep")
501+
if err != nil {
502+
t.Fatal(err)
503+
}
504+
defer os.RemoveAll(dir)
505+
506+
srcPath := filepath.Join(dir, "src")
507+
symlinkPath := filepath.Join(dir, "symlink")
508+
dstPath := filepath.Join(dir, "dst")
509+
510+
err = os.MkdirAll(srcPath, 0777)
511+
if err != nil {
512+
t.Fatal(err)
513+
}
514+
515+
if err = os.Symlink(srcPath, symlinkPath); err != nil {
516+
t.Fatalf("could not create symlink: %v", err)
517+
}
518+
519+
if err = copyFile(symlinkPath, dstPath); err != nil {
520+
t.Fatalf("failed to copy symlink: %s", err)
521+
}
522+
523+
resolvedPath, err := os.Readlink(dstPath)
524+
if err != nil {
525+
t.Fatalf("could not resolve symlink: %s", err)
526+
}
527+
528+
if resolvedPath != srcPath {
529+
t.Fatalf("resolved path is incorrect. expected %s, got %s", srcPath, resolvedPath)
530+
}
531+
}
532+
464533
func TestCopyFileFail(t *testing.T) {
465534
if runtime.GOOS == "windows" {
466535
// XXX: setting permissions works differently in
@@ -659,7 +728,6 @@ func TestIsDir(t *testing.T) {
659728
t.Fatalf("expected %t for %s, got %t", want.exists, f, got)
660729
}
661730
}
662-
663731
}
664732

665733
func TestIsEmpty(t *testing.T) {
@@ -702,3 +770,85 @@ func TestIsEmpty(t *testing.T) {
702770
}
703771
}
704772
}
773+
774+
func TestIsSymlink(t *testing.T) {
775+
if runtime.GOOS == "windows" {
776+
// XXX: creating symlinks is not supported in Go on
777+
// Microsoft Windows. Skipping this this until a solution
778+
// for creating symlinks is is provided.
779+
t.Skip("skipping on windows")
780+
}
781+
782+
dir, err := ioutil.TempDir("", "dep")
783+
if err != nil {
784+
t.Fatal(err)
785+
}
786+
defer os.RemoveAll(dir)
787+
788+
dirPath := filepath.Join(dir, "directory")
789+
if err = os.MkdirAll(dirPath, 0777); err != nil {
790+
t.Fatal(err)
791+
}
792+
793+
filePath := filepath.Join(dir, "file")
794+
f, err := os.Create(filePath)
795+
if err != nil {
796+
t.Fatal(err)
797+
}
798+
f.Close()
799+
800+
dirSymlink := filepath.Join(dir, "dirSymlink")
801+
fileSymlink := filepath.Join(dir, "fileSymlink")
802+
if err = os.Symlink(dirPath, dirSymlink); err != nil {
803+
t.Fatal(err)
804+
}
805+
if err = os.Symlink(filePath, fileSymlink); err != nil {
806+
t.Fatal(err)
807+
}
808+
809+
var (
810+
inaccessibleFile string
811+
inaccessibleSymlink string
812+
)
813+
814+
err, cleanup := setupInaccessibleDir(func(dir string) error {
815+
inaccessibleFile = filepath.Join(dir, "file")
816+
if fh, err := os.Create(inaccessibleFile); err != nil {
817+
return err
818+
} else if err = fh.Close(); err != nil {
819+
return err
820+
}
821+
822+
inaccessibleSymlink = filepath.Join(dir, "symlink")
823+
return os.Symlink(inaccessibleFile, inaccessibleSymlink)
824+
})
825+
defer cleanup()
826+
if err != nil {
827+
t.Fatal(err)
828+
}
829+
830+
tests := map[string]struct {
831+
expected bool
832+
err bool
833+
}{
834+
dirPath: {false, false},
835+
filePath: {false, false},
836+
dirSymlink: {true, false},
837+
fileSymlink: {true, false},
838+
inaccessibleFile: {false, true},
839+
inaccessibleSymlink: {false, true},
840+
}
841+
842+
for path, want := range tests {
843+
got, err := IsSymlink(path)
844+
if err != nil {
845+
if !want.err {
846+
t.Errorf("expected no error, got %v", err)
847+
}
848+
}
849+
850+
if got != want.expected {
851+
t.Errorf("expected %t for %s, got %t", want.expected, path, got)
852+
}
853+
}
854+
}

0 commit comments

Comments
 (0)