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

Commit

Permalink
internal/fs: clone symlinks on Windows and fall back to file copying
Browse files Browse the repository at this point in the history
Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
  • Loading branch information
ibrasho committed Jul 28, 2017
1 parent 55095d2 commit ced76d2
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 59 deletions.
47 changes: 26 additions & 21 deletions internal/fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"syscall"
"unicode"

"github.com/pkg/errors"
Expand Down Expand Up @@ -270,9 +272,25 @@ func CopyDir(src, dst string) error {
// the copied data is synced/flushed to stable storage.
func copyFile(src, dst string) (err error) {
if sym, err := IsSymlink(src); err != nil {
return err
return errors.Wrap(err, "symlink check failed")
} else if sym {
return cloneSymlink(src, dst)
if err := cloneSymlink(src, dst); err != nil {
if runtime.GOOS == "windows" {
// If cloning the symlink fails on Windows because the user
// does not have the required privileges, ignore the error and
// fall back to copying the file contents.
//
// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
return err
}
} else {
return err
}
} else {
return nil
}
}

in, err := os.Open(src)
Expand All @@ -285,30 +303,22 @@ func copyFile(src, dst string) (err error) {
if err != nil {
return
}
defer func() {
if e := out.Close(); e != nil {
err = e
}
}()
defer out.Close()

_, err = io.Copy(out, in)
if err != nil {
if _, err = io.Copy(out, in); err != nil {
return
}

err = out.Sync()
if err != nil {
if err = out.Sync(); err != nil {
return
}

si, err := os.Stat(src)
if err != nil {
return
}

err = os.Chmod(dst, si.Mode())
if err != nil {
return
}

return
}
Expand All @@ -318,15 +328,10 @@ func copyFile(src, dst string) (err error) {
func cloneSymlink(sl, dst string) error {
resolved, err := os.Readlink(sl)
if err != nil {
return errors.Wrap(err, "failed to resolve symlink")
}

err = os.Symlink(resolved, dst)
if err != nil {
return errors.Wrapf(err, "failed to create symlink %s to %s", dst, resolved)
return err
}

return nil
return os.Symlink(resolved, dst)
}

// IsDir determines is the path given is a directory or not.
Expand Down
73 changes: 35 additions & 38 deletions internal/fs/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,43 +504,46 @@ func TestCopyFileSymlink(t *testing.T) {
h.TempDir(".")

testcases := map[string]string{
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"),
filepath.Join("./testdata/symlinks/dir-symlink"): filepath.Join(h.Path("."), "dst-dir"),
filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"),
filepath.Join("./testdata/symlinks/file-symlink"): filepath.Join(h.Path("."), "dst-file"),
filepath.Join("./testdata/symlinks/windows-file-symlink"): filepath.Join(h.Path("."), "windows-dst-file"),
filepath.Join("./testdata/symlinks/dir-symlink"): filepath.Join(h.Path("."), "dst-dir"),
filepath.Join("./testdata/symlinks/invalid-symlink"): filepath.Join(h.Path("."), "invalid-symlink"),
}

for symlink, dst := range testcases {
var err error
if err = copyFile(symlink, dst); err != nil {
t.Fatalf("failed to copy symlink: %s", err)
}

var want, got string

if runtime.GOOS == "windows" {
// Creating symlinks on Windows require an additional permission
// regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink.
srcb, err := ioutil.ReadFile(symlink)
h.Must(err)
dstb, err := ioutil.ReadFile(dst)
h.Must(err)

want = string(srcb)
got = string(dstb)
} else {
want, err = os.Readlink(symlink)
h.Must(err)
t.Run(symlink, func(t *testing.T) {
var err error
if err = copyFile(symlink, dst); err != nil {
t.Fatalf("failed to copy symlink: %s", err)
}

got, err = os.Readlink(dst)
if err != nil {
t.Fatalf("could not resolve symlink: %s", err)
var want, got string

if runtime.GOOS == "windows" {
// Creating symlinks on Windows require an additional permission
// regular users aren't granted usually. So we copy the file
// content as a fall back instead of creating a real symlink.
srcb, err := ioutil.ReadFile(symlink)
h.Must(err)
dstb, err := ioutil.ReadFile(dst)
h.Must(err)

want = string(srcb)
got = string(dstb)
} else {
want, err = os.Readlink(symlink)
h.Must(err)

got, err = os.Readlink(dst)
if err != nil {
t.Fatalf("could not resolve symlink: %s", err)
}
}
}

if want != got {
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
}
if want != got {
t.Fatalf("resolved path is incorrect. expected %s, got %s", want, got)
}
})
}
}

Expand Down Expand Up @@ -791,13 +794,6 @@ func TestIsNonEmptyDir(t *testing.T) {
}

func TestIsSymlink(t *testing.T) {
if runtime.GOOS == "windows" {
// XXX: creating symlinks is not supported in Go on
// Microsoft Windows. Skipping this this until a solution
// for creating symlinks is is provided.
t.Skip("skipping on windows")
}

dir, err := ioutil.TempDir("", "dep")
if err != nil {
t.Fatal(err)
Expand All @@ -818,6 +814,7 @@ func TestIsSymlink(t *testing.T) {

dirSymlink := filepath.Join(dir, "dirSymlink")
fileSymlink := filepath.Join(dir, "fileSymlink")

if err = os.Symlink(dirPath, dirSymlink); err != nil {
t.Fatal(err)
}
Expand Down
1 change: 1 addition & 0 deletions internal/fs/testdata/symlinks/windows-file-symlink

0 comments on commit ced76d2

Please sign in to comment.