Skip to content

Commit 34d3314

Browse files
committed
internal/fs: don't clone symlinks on windows
copyFile calls copySymlink on Windows which fails if the user doesn't have the required permission. This is a very common case since symlinks are used heavily on Windows. This change renames copySymlink to cloneSymlink to clarify the intention and skips calling it when on Windows to fallback to copy the file content instead. Fixes golang#773 Signed-off-by: Ibrahim AshShohail <[email protected]>
1 parent c79b048 commit 34d3314

File tree

5 files changed

+51
-66
lines changed

5 files changed

+51
-66
lines changed

internal/fs/fs.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io/ioutil"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"strings"
1314
"unicode"
1415

@@ -269,12 +270,16 @@ func CopyDir(src, dst string) error {
269270
// of the source file. The file mode will be copied from the source and
270271
// the copied data is synced/flushed to stable storage.
271272
func copyFile(src, dst string) (err error) {
272-
if sym, err := IsSymlink(src); err != nil {
273-
return err
274-
} else if sym {
275-
err := copySymlink(src, dst)
273+
sym, err := IsSymlink(src)
274+
if err != nil {
276275
return err
277276
}
277+
// Creating symlinks on Windows require an additional permission regular
278+
// users aren't granted usually. So we skip cloning the symlink nd copy the
279+
// file content as a fall back instead.
280+
if sym && runtime.GOOS != "windows" {
281+
return cloneSymlink(src, dst)
282+
}
278283

279284
in, err := os.Open(src)
280285
if err != nil {
@@ -314,17 +319,17 @@ func copyFile(src, dst string) (err error) {
314319
return
315320
}
316321

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)
322+
// cloneSymlink will create a new symlink that points to the resolved path of sl.
323+
// If sl is a relative symlink, dst will also be a relative symlink.
324+
func cloneSymlink(sl, dst string) error {
325+
resolved, err := os.Readlink(sl)
321326
if err != nil {
322327
return errors.Wrap(err, "failed to resolve symlink")
323328
}
324329

325330
err = os.Symlink(resolved, dst)
326331
if err != nil {
327-
return errors.Wrapf(err, "failed to create symlink %s to %s", src, resolved)
332+
return errors.Wrapf(err, "failed to create symlink %s to %s", dst, resolved)
328333
}
329334

330335
return nil

internal/fs/fs_test.go

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -499,71 +499,48 @@ 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-
}
502+
h := test.NewHelper(t)
503+
defer h.Cleanup()
504+
h.TempDir(".")
535505

536-
func TestCopyFileSymlinkToDirectory(t *testing.T) {
537-
dir, err := ioutil.TempDir("", "dep")
538-
if err != nil {
539-
t.Fatal(err)
506+
testcases := map[string]string{
507+
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"),
508+
filepath.Join("./testdata/symlinks/dir-symlink"): filepath.Join(h.Path("."), "dst-dir"),
509+
filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"),
540510
}
541-
defer os.RemoveAll(dir)
542511

543-
srcPath := filepath.Join(dir, "src")
544-
symlinkPath := filepath.Join(dir, "symlink")
545-
dstPath := filepath.Join(dir, "dst")
512+
for symlink, dst := range testcases {
513+
var err error
514+
if err = copyFile(symlink, dst); err != nil {
515+
t.Fatalf("failed to copy symlink: %s", err)
516+
}
546517

547-
err = os.MkdirAll(srcPath, 0777)
548-
if err != nil {
549-
t.Fatal(err)
550-
}
518+
var want, got string
551519

552-
if err = os.Symlink(srcPath, symlinkPath); err != nil {
553-
t.Fatalf("could not create symlink: %v", err)
554-
}
520+
if runtime.GOOS == "window" {
521+
// Creating symlinks on Windows require an additional permission
522+
// regular users aren't granted usually. So we copy the file
523+
// content as a fall back instead of creating a real symlink.
524+
srcb, err := ioutil.ReadFile(symlink)
525+
h.Must(err)
526+
dstb, err := ioutil.ReadFile(dst)
527+
h.Must(err)
555528

556-
if err = copyFile(symlinkPath, dstPath); err != nil {
557-
t.Fatalf("failed to copy symlink: %s", err)
558-
}
529+
want = string(srcb)
530+
got = string(dstb)
531+
} else {
532+
want, err = os.Readlink(symlink)
533+
h.Must(err)
559534

560-
resolvedPath, err := os.Readlink(dstPath)
561-
if err != nil {
562-
t.Fatalf("could not resolve symlink: %s", err)
563-
}
535+
got, err = os.Readlink(dst)
536+
if err != nil {
537+
t.Fatalf("could not resolve symlink: %s", err)
538+
}
539+
}
564540

565-
if resolvedPath != srcPath {
566-
t.Fatalf("resolved path is incorrect. expected %s, got %s", srcPath, resolvedPath)
541+
if want != got {
542+
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
543+
}
567544
}
568545
}
569546

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../testdata
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../test.file
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/non/existing/file

0 commit comments

Comments
 (0)