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

Commit 44a454b

Browse files
authored
Merge pull request #781 from ibrasho-forks/fix-symlink-cloning-on-windows
internal/fs: don't clone symlinks on windows
2 parents 8a46f4d + ced76d2 commit 44a454b

File tree

6 files changed

+83
-97
lines changed

6 files changed

+83
-97
lines changed

Diff for: internal/fs/fs.go

+30-26
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"io/ioutil"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"strings"
14+
"syscall"
1315
"unicode"
1416

1517
"github.com/pkg/errors"
@@ -270,10 +272,25 @@ func CopyDir(src, dst string) error {
270272
// the copied data is synced/flushed to stable storage.
271273
func copyFile(src, dst string) (err error) {
272274
if sym, err := IsSymlink(src); err != nil {
273-
return err
275+
return errors.Wrap(err, "symlink check failed")
274276
} else if sym {
275-
err := copySymlink(src, dst)
276-
return err
277+
if err := cloneSymlink(src, dst); err != nil {
278+
if runtime.GOOS == "windows" {
279+
// If cloning the symlink fails on Windows because the user
280+
// does not have the required privileges, ignore the error and
281+
// fall back to copying the file contents.
282+
//
283+
// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
284+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
285+
if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
286+
return err
287+
}
288+
} else {
289+
return err
290+
}
291+
} else {
292+
return nil
293+
}
277294
}
278295

279296
in, err := os.Open(src)
@@ -286,48 +303,35 @@ func copyFile(src, dst string) (err error) {
286303
if err != nil {
287304
return
288305
}
289-
defer func() {
290-
if e := out.Close(); e != nil {
291-
err = e
292-
}
293-
}()
306+
defer out.Close()
294307

295-
_, err = io.Copy(out, in)
296-
if err != nil {
308+
if _, err = io.Copy(out, in); err != nil {
297309
return
298310
}
299311

300-
err = out.Sync()
301-
if err != nil {
312+
if err = out.Sync(); err != nil {
302313
return
303314
}
304315

305316
si, err := os.Stat(src)
306317
if err != nil {
307318
return
308319
}
320+
309321
err = os.Chmod(dst, si.Mode())
310-
if err != nil {
311-
return
312-
}
313322

314323
return
315324
}
316325

317-
// copySymlink will resolve the src symlink and create a new symlink in dst.
318-
// If src is a relative symlink, dst will also be a relative symlink.
319-
func copySymlink(src, dst string) error {
320-
resolved, err := os.Readlink(src)
326+
// cloneSymlink will create a new symlink that points to the resolved path of sl.
327+
// If sl is a relative symlink, dst will also be a relative symlink.
328+
func cloneSymlink(sl, dst string) error {
329+
resolved, err := os.Readlink(sl)
321330
if err != nil {
322-
return errors.Wrap(err, "failed to resolve symlink")
323-
}
324-
325-
err = os.Symlink(resolved, dst)
326-
if err != nil {
327-
return errors.Wrapf(err, "failed to create symlink %s to %s", src, resolved)
331+
return err
328332
}
329333

330-
return nil
334+
return os.Symlink(resolved, dst)
331335
}
332336

333337
// IsDir determines is the path given is a directory or not.

Diff for: internal/fs/fs_test.go

+49-71
Original file line numberDiff line numberDiff line change
@@ -499,71 +499,51 @@ func TestCopyFile(t *testing.T) {
499499
}
500500

501501
func TestCopyFileSymlink(t *testing.T) {
502-
dir, err := ioutil.TempDir("", "dep")
503-
if err != nil {
504-
t.Fatal(err)
505-
}
506-
defer os.RemoveAll(dir)
507-
508-
srcPath := filepath.Join(dir, "src")
509-
symlinkPath := filepath.Join(dir, "symlink")
510-
dstPath := filepath.Join(dir, "dst")
511-
512-
srcf, err := os.Create(srcPath)
513-
if err != nil {
514-
t.Fatal(err)
515-
}
516-
srcf.Close()
517-
518-
if err = os.Symlink(srcPath, symlinkPath); err != nil {
519-
t.Fatalf("could not create symlink: %s", err)
520-
}
521-
522-
if err = copyFile(symlinkPath, dstPath); err != nil {
523-
t.Fatalf("failed to copy symlink: %s", err)
524-
}
525-
526-
resolvedPath, err := os.Readlink(dstPath)
527-
if err != nil {
528-
t.Fatalf("could not resolve symlink: %s", err)
529-
}
530-
531-
if resolvedPath != srcPath {
532-
t.Fatalf("resolved path is incorrect. expected %s, got %s", srcPath, resolvedPath)
533-
}
534-
}
535-
536-
func TestCopyFileSymlinkToDirectory(t *testing.T) {
537-
dir, err := ioutil.TempDir("", "dep")
538-
if err != nil {
539-
t.Fatal(err)
540-
}
541-
defer os.RemoveAll(dir)
542-
543-
srcPath := filepath.Join(dir, "src")
544-
symlinkPath := filepath.Join(dir, "symlink")
545-
dstPath := filepath.Join(dir, "dst")
546-
547-
err = os.MkdirAll(srcPath, 0777)
548-
if err != nil {
549-
t.Fatal(err)
550-
}
502+
h := test.NewHelper(t)
503+
defer h.Cleanup()
504+
h.TempDir(".")
551505

552-
if err = os.Symlink(srcPath, symlinkPath); err != nil {
553-
t.Fatalf("could not create symlink: %v", err)
506+
testcases := map[string]string{
507+
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"),
508+
filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(h.Path("."), "windows-dst-file"),
509+
filepath.Join("./testdata/symlinks/dir-symlink"): filepath.Join(h.Path("."), "dst-dir"),
510+
filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"),
554511
}
555512

556-
if err = copyFile(symlinkPath, dstPath); err != nil {
557-
t.Fatalf("failed to copy symlink: %s", err)
558-
}
513+
for symlink, dst := range testcases {
514+
t.Run(symlink, func(t *testing.T) {
515+
var err error
516+
if err = copyFile(symlink, dst); err != nil {
517+
t.Fatalf("failed to copy symlink: %s", err)
518+
}
559519

560-
resolvedPath, err := os.Readlink(dstPath)
561-
if err != nil {
562-
t.Fatalf("could not resolve symlink: %s", err)
563-
}
520+
var want, got string
521+
522+
if runtime.GOOS == "windows" {
523+
// Creating symlinks on Windows require an additional permission
524+
// regular users aren't granted usually. So we copy the file
525+
// content as a fall back instead of creating a real symlink.
526+
srcb, err := ioutil.ReadFile(symlink)
527+
h.Must(err)
528+
dstb, err := ioutil.ReadFile(dst)
529+
h.Must(err)
530+
531+
want = string(srcb)
532+
got = string(dstb)
533+
} else {
534+
want, err = os.Readlink(symlink)
535+
h.Must(err)
536+
537+
got, err = os.Readlink(dst)
538+
if err != nil {
539+
t.Fatalf("could not resolve symlink: %s", err)
540+
}
541+
}
564542

565-
if resolvedPath != srcPath {
566-
t.Fatalf("resolved path is incorrect. expected %s, got %s", srcPath, resolvedPath)
543+
if want != got {
544+
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
545+
}
546+
})
567547
}
568548
}
569549

@@ -814,13 +794,6 @@ func TestIsNonEmptyDir(t *testing.T) {
814794
}
815795

816796
func TestIsSymlink(t *testing.T) {
817-
if runtime.GOOS == "windows" {
818-
// XXX: creating symlinks is not supported in Go on
819-
// Microsoft Windows. Skipping this this until a solution
820-
// for creating symlinks is is provided.
821-
t.Skip("skipping on windows")
822-
}
823-
824797
dir, err := ioutil.TempDir("", "dep")
825798
if err != nil {
826799
t.Fatal(err)
@@ -841,6 +814,7 @@ func TestIsSymlink(t *testing.T) {
841814

842815
dirSymlink := filepath.Join(dir, "dirSymlink")
843816
fileSymlink := filepath.Join(dir, "fileSymlink")
817+
844818
if err = os.Symlink(dirPath, dirSymlink); err != nil {
845819
t.Fatal(err)
846820
}
@@ -866,10 +840,7 @@ func TestIsSymlink(t *testing.T) {
866840
})
867841
defer cleanup()
868842

869-
tests := map[string]struct {
870-
expected bool
871-
err bool
872-
}{
843+
tests := map[string]struct{ expected, err bool }{
873844
dirPath: {false, false},
874845
filePath: {false, false},
875846
dirSymlink: {true, false},
@@ -878,6 +849,13 @@ func TestIsSymlink(t *testing.T) {
878849
inaccessibleSymlink: {false, true},
879850
}
880851

852+
if runtime.GOOS == "windows" {
853+
// XXX: setting permissions works differently in Windows. Skipping
854+
// these cases until a compatible implementation is provided.
855+
delete(tests, inaccessibleFile)
856+
delete(tests, inaccessibleSymlink)
857+
}
858+
881859
for path, want := range tests {
882860
got, err := IsSymlink(path)
883861
if err != nil {

Diff for: internal/fs/testdata/symlinks/dir-symlink

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../testdata

Diff for: internal/fs/testdata/symlinks/file-symlink

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../test.file

Diff for: internal/fs/testdata/symlinks/invalid-symlink

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/non/existing/file

Diff for: internal/fs/testdata/symlinks/windows-file-symlink

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C:/Users/ibrahim/go/src/github.com/golang/dep/internal/fs/testdata/test.file

0 commit comments

Comments
 (0)