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

Commit ced76d2

Browse files
committed
internal/fs: clone symlinks on Windows and fall back to file copying
Signed-off-by: Ibrahim AshShohail <[email protected]>
1 parent 55095d2 commit ced76d2

File tree

3 files changed

+62
-59
lines changed

3 files changed

+62
-59
lines changed

internal/fs/fs.go

Lines changed: 26 additions & 21 deletions
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,9 +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-
return cloneSymlink(src, dst)
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+
}
276294
}
277295

278296
in, err := os.Open(src)
@@ -285,30 +303,22 @@ func copyFile(src, dst string) (err error) {
285303
if err != nil {
286304
return
287305
}
288-
defer func() {
289-
if e := out.Close(); e != nil {
290-
err = e
291-
}
292-
}()
306+
defer out.Close()
293307

294-
_, err = io.Copy(out, in)
295-
if err != nil {
308+
if _, err = io.Copy(out, in); err != nil {
296309
return
297310
}
298311

299-
err = out.Sync()
300-
if err != nil {
312+
if err = out.Sync(); err != nil {
301313
return
302314
}
303315

304316
si, err := os.Stat(src)
305317
if err != nil {
306318
return
307319
}
320+
308321
err = os.Chmod(dst, si.Mode())
309-
if err != nil {
310-
return
311-
}
312322

313323
return
314324
}
@@ -318,15 +328,10 @@ func copyFile(src, dst string) (err error) {
318328
func cloneSymlink(sl, dst string) error {
319329
resolved, err := os.Readlink(sl)
320330
if err != nil {
321-
return errors.Wrap(err, "failed to resolve symlink")
322-
}
323-
324-
err = os.Symlink(resolved, dst)
325-
if err != nil {
326-
return errors.Wrapf(err, "failed to create symlink %s to %s", dst, resolved)
331+
return err
327332
}
328333

329-
return nil
334+
return os.Symlink(resolved, dst)
330335
}
331336

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

internal/fs/fs_test.go

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -504,43 +504,46 @@ func TestCopyFileSymlink(t *testing.T) {
504504
h.TempDir(".")
505505

506506
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"),
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"),
510511
}
511512

512513
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-
}
517-
518-
var want, got string
519-
520-
if runtime.GOOS == "windows" {
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)
528-
529-
want = string(srcb)
530-
got = string(dstb)
531-
} else {
532-
want, err = os.Readlink(symlink)
533-
h.Must(err)
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+
}
534519

535-
got, err = os.Readlink(dst)
536-
if err != nil {
537-
t.Fatalf("could not resolve symlink: %s", err)
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+
}
538541
}
539-
}
540542

541-
if want != got {
542-
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
543-
}
543+
if want != got {
544+
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
545+
}
546+
})
544547
}
545548
}
546549

@@ -791,13 +794,6 @@ func TestIsNonEmptyDir(t *testing.T) {
791794
}
792795

793796
func TestIsSymlink(t *testing.T) {
794-
if runtime.GOOS == "windows" {
795-
// XXX: creating symlinks is not supported in Go on
796-
// Microsoft Windows. Skipping this this until a solution
797-
// for creating symlinks is is provided.
798-
t.Skip("skipping on windows")
799-
}
800-
801797
dir, err := ioutil.TempDir("", "dep")
802798
if err != nil {
803799
t.Fatal(err)
@@ -818,6 +814,7 @@ func TestIsSymlink(t *testing.T) {
818814

819815
dirSymlink := filepath.Join(dir, "dirSymlink")
820816
fileSymlink := filepath.Join(dir, "fileSymlink")
817+
821818
if err = os.Symlink(dirPath, dirSymlink); err != nil {
822819
t.Fatal(err)
823820
}
Lines changed: 1 addition & 0 deletions
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)