Skip to content

Commit 6054081

Browse files
authored
Merge pull request ActiveState#3600 from ActiveState/mitchell/dx-3167-2
Handle recursive links in runtime sources.
2 parents fbf7ca2 + d1a7e18 commit 6054081

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

internal/smartlink/smartlink.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,25 @@ func LinkContents(src, dest string) error {
4040
// Link creates a link from src to target. MS decided to support Symlinks but only if you opt into developer mode (go figure),
4141
// which we cannot reasonably force on our users. So on Windows we will instead create dirs and hardlinks.
4242
func Link(src, dest string) error {
43+
originalSrc := src
44+
4345
var err error
4446
src, dest, err = resolvePaths(src, dest)
4547
if err != nil {
4648
return errs.Wrap(err, "Could not resolve src and dest paths")
4749
}
4850

4951
if fileutils.IsDir(src) {
52+
if fileutils.IsSymlink(originalSrc) {
53+
// If the original src is a symlink, the resolved src is no longer a symlink and could point
54+
// to a parent directory, resulting in a recursive directory structure.
55+
// Avoid any potential problems by simply linking the original link to the target.
56+
// Links to directories are okay on Linux and macOS, but will fail on Windows.
57+
// If we ever get here on Windows, the artifact being deployed is bad and there's nothing we
58+
// can do about it except receive the report from Rollbar and report it internally.
59+
return linkFile(originalSrc, dest)
60+
}
61+
5062
if err := fileutils.Mkdir(dest); err != nil {
5163
return errs.Wrap(err, "could not create directory %s", dest)
5264
}

internal/smartlink/smartlink_lin_mac.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,10 @@ package smartlink
55

66
import (
77
"os"
8-
9-
"github.com/ActiveState/cli/internal/errs"
10-
"github.com/ActiveState/cli/internal/fileutils"
118
)
129

1310
// file will create a symlink from src to dest, and falls back on a hardlink if no symlink is available.
1411
// This is a workaround for the fact that Windows does not support symlinks without admin privileges.
1512
func linkFile(src, dest string) error {
16-
if fileutils.IsDir(src) {
17-
return errs.New("src is a directory, not a file: %s", src)
18-
}
1913
return os.Symlink(src, dest)
2014
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package smartlink
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"runtime"
7+
"testing"
8+
9+
"github.com/ActiveState/cli/internal/fileutils"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestLinkContentsWithCircularLink(t *testing.T) {
14+
srcDir, err := os.MkdirTemp("", "src")
15+
require.NoError(t, err)
16+
defer os.RemoveAll(srcDir)
17+
18+
destDir, err := os.MkdirTemp("", "dest")
19+
require.NoError(t, err)
20+
defer os.RemoveAll(destDir)
21+
22+
// Create test file structure:
23+
// src/
24+
// ├── regular.txt
25+
// └── subdir/
26+
// ├── circle -> subdir (circular link)
27+
// └── subfile.txt
28+
29+
testFile := filepath.Join(srcDir, "regular.txt")
30+
err = os.WriteFile(testFile, []byte("test content"), 0644)
31+
require.NoError(t, err)
32+
33+
subDir := filepath.Join(srcDir, "subdir")
34+
err = os.Mkdir(subDir, 0755)
35+
require.NoError(t, err)
36+
37+
subFile := filepath.Join(subDir, "subfile.txt")
38+
err = os.WriteFile(subFile, []byte("sub content"), 0644)
39+
require.NoError(t, err)
40+
41+
circularLink := filepath.Join(subDir, "circle")
42+
err = os.Symlink(subDir, circularLink)
43+
require.NoError(t, err)
44+
45+
err = LinkContents(srcDir, destDir)
46+
if runtime.GOOS == "windows" {
47+
require.Error(t, err)
48+
return // hard links to directories are not allowed on Windows
49+
}
50+
require.NoError(t, err)
51+
52+
// Verify file structure.
53+
destFile := filepath.Join(destDir, "regular.txt")
54+
require.FileExists(t, destFile)
55+
content, err := os.ReadFile(destFile)
56+
require.NoError(t, err)
57+
require.Equal(t, "test content", string(content))
58+
59+
destSubFile := filepath.Join(destDir, "subdir", "subfile.txt")
60+
require.FileExists(t, destSubFile)
61+
subContent, err := os.ReadFile(destSubFile)
62+
require.NoError(t, err)
63+
require.Equal(t, "sub content", string(subContent))
64+
65+
destCircular := filepath.Join(destDir, "subdir", "circle")
66+
require.FileExists(t, destCircular)
67+
target, err := fileutils.ResolveUniquePath(destCircular)
68+
require.NoError(t, err)
69+
srcCircular := filepath.Join(srcDir, "subdir")
70+
if runtime.GOOS == "darwin" {
71+
srcCircular, err = fileutils.ResolveUniquePath(srcCircular) // needed for full $TMPDIR resolution
72+
require.NoError(t, err)
73+
}
74+
require.Equal(t, target, srcCircular)
75+
}

0 commit comments

Comments
 (0)