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

internal/fs: handle symlinks in copyFile() #657

Merged
merged 8 commits into from
May 30, 2017

Conversation

ibrasho
Copy link
Collaborator

@ibrasho ibrasho commented May 26, 2017

fs.copyFile() currently fails when src is a symlink to a directory.

This PR adds 2 functions:

  • fs.IsSymlink() to check if a path is a symbolic link.
  • fs.copySymlink() to copy a symlink (preserves relative symlinks)

And update fs.copyFile() to handle symlinks correctly.

Should solve #651 .

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
@ibrasho
Copy link
Collaborator Author

ibrasho commented May 26, 2017

I borrowed some functions from #641 . 😉

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
@@ -242,6 +242,13 @@ func CopyDir(src, dst string) error {
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
func copyFile(src, dst string) (err error) {
if isSymlink, err := IsSymlink(src); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a short function like this, using a shorter variable name - e.g., is or sym instead of isSymlink - is more idiomatic.

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
}

for path, want := range tests {
if runtime.GOOS == "windows" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this at the top of the test before doing any setup work

got, err := IsSymlink(path)
if err != nil {
if !want.err {
t.Fatalf("expected no error, got %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use t.Errorf(); continue

so that you get all the test failures, not just the first

@@ -242,6 +242,13 @@ func CopyDir(src, dst string) error {
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
func copyFile(src, dst string) (err error) {
if sym, err := IsSymlink(src); err != nil {
return errors.Wrapf(err, "could not lstat %s", src)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return the error, it's already wrapped

}

err = os.Symlink(resolved, dst)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can write this

return errors.Wrapf(err, "...")

if err == nil, errors.Wrapf returns nil.

This might be too magical.

@@ -337,3 +360,13 @@ func IsRegular(name string) (bool, error) {
}
return true, nil
}

// IsSymlink determines if the given path is a symbolic link.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a note to describe the behaviour of this function on Windows.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should behave in the same way. os.Lstat() call GetFileAttributesEx on Windows and FileStat.Mode() will convert the attribute syscall.FILE_ATTRIBUTE_REPARSE_POINT to os.ModeSymlink. (see os/stat_windows.go and os/types_windows.go).

Unless I'm missing something else. 😄

Copy link
Collaborator Author

@ibrasho ibrasho May 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why they the tests for this function are skipped is that creating a symlink (using os.Symlink()) is not supported on Windows. But if the symlink already exists, it will be detected by this function.

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small style nits, then LGTM


err = os.Symlink(resolved, dst)

return errors.Wrapf(err, "failed to create symlink %s to %s", src, resolved)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awkwardly halfway between a terse form and the standard form. Either combine this and the preceding line into one (errors.Wrapf(os.Symlink(resolved, dst), ...)), or do the more standard if err != nil check.

}

inaccessibleSymlink = filepath.Join(dir, "symlink")
err = os.Symlink(inaccessibleFile, inaccessibleSymlink)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return this final call directly, no need to assign into var

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
@ibrasho
Copy link
Collaborator Author

ibrasho commented May 30, 2017

Done.

@sdboyer
Copy link
Member

sdboyer commented May 30, 2017

great, thanks! 🎉

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants