Skip to content

Commit aac0572

Browse files
authored
avoid text file busy error on track (#2494)
* avoid text file busy error on track * moved exec setup to utils and added unit * lint * fix * address PR comments
1 parent 4bf7b88 commit aac0572

File tree

4 files changed

+120
-22
lines changed

4 files changed

+120
-22
lines changed

pkg/localnet/tmpnet.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -376,19 +376,18 @@ func TmpNetSetDefaultAliases(ctx context.Context, networkDir string) error {
376376

377377
// Install the given VM binary into the appropriate location with the
378378
// appropriate name
379-
func TmpNetInstallVM(network *tmpnet.Network, binaryPath string, vmID ids.ID) error {
379+
func TmpNetInstallVM(
380+
log logging.Logger,
381+
network *tmpnet.Network,
382+
binaryPath string,
383+
vmID ids.ID,
384+
) error {
380385
pluginDir, err := network.DefaultFlags.GetStringVal(config.PluginDirKey)
381386
if err != nil {
382387
return err
383388
}
384389
pluginPath := filepath.Join(pluginDir, vmID.String())
385-
if err := utils.FileCopy(binaryPath, pluginPath); err != nil {
386-
return err
387-
}
388-
if err := os.Chmod(pluginPath, constants.DefaultPerms755); err != nil {
389-
return err
390-
}
391-
return nil
390+
return utils.SetupExecFile(log, binaryPath, pluginPath)
392391
}
393392

394393
// Set up blockchain config for all nodes in the network
@@ -589,7 +588,7 @@ func TmpNetTrackSubnet(
589588
if err != nil {
590589
return err
591590
}
592-
if err := TmpNetInstallVM(network, vmBinaryPath, vmID); err != nil {
591+
if err := TmpNetInstallVM(log, network, vmBinaryPath, vmID); err != nil {
593592
return err
594593
}
595594
// Configs

pkg/node/local.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,9 @@ func TrackSubnetWithLocalMachine(
7777
return fmt.Errorf("unknown vm: %s", sc.VM)
7878
}
7979
rootDir := app.GetLocalDir(clusterName)
80+
8081
pluginPath := filepath.Join(rootDir, "node1", "plugins", vmID.String())
81-
if err := utils.FileCopy(vmBin, pluginPath); err != nil {
82-
return err
83-
}
84-
if err := os.Chmod(pluginPath, constants.DefaultPerms755); err != nil {
82+
if err := utils.SetupExecFile(app.Log, vmBin, pluginPath); err != nil {
8583
return err
8684
}
8785

pkg/utils/file.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99

1010
"github.com/ava-labs/avalanche-cli/pkg/constants"
1111
sdkutils "github.com/ava-labs/avalanche-cli/sdk/utils"
12+
"github.com/ava-labs/avalanchego/utils/logging"
13+
14+
"go.uber.org/zap"
1215
"golang.org/x/mod/modfile"
1316
)
1417

@@ -38,7 +41,11 @@ func IsExecutable(filename string) bool {
3841
return false
3942
}
4043
info, _ := os.Stat(filename)
41-
return info.Mode()&0x0100 != 0
44+
// A file perm can be seen as a 9-bit sequence mapping to: rwxrwxrwx
45+
// read-write-execute for owner, group and everybody.
46+
// 0o100 is the bit mask that, when applied with the bitwise-AND operator &,
47+
// results in != 0 state whenever the file can be executed by the owner.
48+
return info.Mode()&0o100 != 0
4249
}
4350

4451
// UserHomePath returns the absolute path of a file located in the user's home directory.
@@ -77,6 +84,34 @@ func FileCopy(src string, dst string) error {
7784
return os.WriteFile(dst, data, constants.WriteReadReadPerms)
7885
}
7986

87+
// SetupExecFile copies a file into destination and set it to have exec perms,
88+
// if destination either does not exists, or is not executable
89+
func SetupExecFile(
90+
log logging.Logger,
91+
src string,
92+
dst string,
93+
) error {
94+
if !IsExecutable(dst) {
95+
if FileExists(dst) {
96+
log.Error(
97+
"binary was not properly installed on a previous CLI execution",
98+
zap.String("binary-path", dst),
99+
)
100+
}
101+
// Either it was never installed, or it was partially done (copy or chmod
102+
// failure)
103+
// As the file is not executable, there is no risk of encountering text file busy
104+
// error during copy, because that happens when the binary is being executed.
105+
if err := FileCopy(src, dst); err != nil {
106+
return err
107+
}
108+
if err := os.Chmod(dst, constants.DefaultPerms755); err != nil {
109+
return err
110+
}
111+
}
112+
return nil
113+
}
114+
80115
// ReadFile reads a file and returns the contents as a string
81116
func ReadFile(filePath string) (string, error) {
82117
filePath = ExpandHome(filePath)

pkg/utils/file_test.go

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import (
66
"os"
77
"path/filepath"
88
"testing"
9+
10+
"github.com/ava-labs/avalanche-cli/pkg/constants"
11+
"github.com/ava-labs/avalanchego/utils/logging"
12+
13+
"github.com/stretchr/testify/require"
914
)
1015

1116
func TestExpandHome(t *testing.T) {
@@ -45,29 +50,26 @@ func TestExpandHome(t *testing.T) {
4550
}
4651
}
4752

48-
// createTempGoMod creates a temporary go.mod file with the provided content.
49-
func createTempGoMod(t *testing.T, content string) string {
53+
// createTemp creates a temporary file with the provided name prefix and content.
54+
func createTemp(t *testing.T, namePrefix string, content string) string {
5055
t.Helper()
51-
file, err := os.CreateTemp("", "go.mod")
56+
file, err := os.CreateTemp("", namePrefix)
5257
if err != nil {
5358
t.Fatal(err)
5459
}
55-
5660
if _, err := file.Write([]byte(content)); err != nil {
5761
t.Fatal(err)
5862
}
59-
6063
if err := file.Close(); err != nil {
6164
t.Fatal(err)
6265
}
63-
6466
return file.Name()
6567
}
6668

6769
// TestReadGoVersion tests all scenarios in one function using sub-tests.
6870
func TestReadGoVersion(t *testing.T) {
6971
t.Run("Success", func(t *testing.T) {
70-
tempFile := createTempGoMod(t, "module example.com/test\n\ngo 1.23\n")
72+
tempFile := createTemp(t, "go.mod", "module example.com/test\n\ngo 1.23\n")
7173
defer os.Remove(tempFile) // Clean up the temp file
7274

7375
version, err := ReadGoVersion(tempFile)
@@ -82,7 +84,7 @@ func TestReadGoVersion(t *testing.T) {
8284
})
8385

8486
t.Run("NoVersion", func(t *testing.T) {
85-
tempFile := createTempGoMod(t, "module example.com/test\n")
87+
tempFile := createTemp(t, "go.mod", "module example.com/test\n")
8688
defer os.Remove(tempFile)
8789

8890
_, err := ReadGoVersion(tempFile)
@@ -98,3 +100,67 @@ func TestReadGoVersion(t *testing.T) {
98100
}
99101
})
100102
}
103+
104+
func TestSetupExecFile(t *testing.T) {
105+
srcContent := "src content"
106+
destContent := "dest content"
107+
t.Run("Src does not exists", func(t *testing.T) {
108+
src := createTemp(t, "testexecfile", srcContent)
109+
dest := createTemp(t, "testexecfile", destContent)
110+
err := os.Remove(src)
111+
require.NoError(t, err)
112+
require.Equal(t, false, FileExists(src))
113+
require.Equal(t, true, FileExists(dest))
114+
require.Equal(t, false, IsExecutable(dest))
115+
err = SetupExecFile(logging.NoLog{}, src, dest)
116+
require.Error(t, err)
117+
content, err := os.ReadFile(dest)
118+
require.NoError(t, err)
119+
require.Equal(t, true, FileExists(dest))
120+
require.Equal(t, false, IsExecutable(dest))
121+
require.Equal(t, destContent, string(content))
122+
})
123+
t.Run("Dest does not exists", func(t *testing.T) {
124+
src := createTemp(t, "testexecfile", srcContent)
125+
dest := createTemp(t, "testexecfile", destContent)
126+
err := os.Remove(dest)
127+
require.NoError(t, err)
128+
require.Equal(t, false, FileExists(dest))
129+
require.Equal(t, false, IsExecutable(dest))
130+
err = SetupExecFile(logging.NoLog{}, src, dest)
131+
require.NoError(t, err)
132+
content, err := os.ReadFile(dest)
133+
require.NoError(t, err)
134+
require.Equal(t, true, FileExists(dest))
135+
require.Equal(t, true, IsExecutable(dest))
136+
require.Equal(t, srcContent, string(content))
137+
})
138+
t.Run("Dest is not executable", func(t *testing.T) {
139+
src := createTemp(t, "testexecfile", srcContent)
140+
dest := createTemp(t, "testexecfile", destContent)
141+
require.Equal(t, true, FileExists(dest))
142+
require.Equal(t, false, IsExecutable(dest))
143+
err := SetupExecFile(logging.NoLog{}, src, dest)
144+
require.NoError(t, err)
145+
content, err := os.ReadFile(dest)
146+
require.NoError(t, err)
147+
require.Equal(t, true, FileExists(dest))
148+
require.Equal(t, true, IsExecutable(dest))
149+
require.Equal(t, srcContent, string(content))
150+
})
151+
t.Run("Dest is already executable", func(t *testing.T) {
152+
src := createTemp(t, "testexecfile", srcContent)
153+
dest := createTemp(t, "testexecfile", destContent)
154+
err := os.Chmod(dest, constants.DefaultPerms755)
155+
require.NoError(t, err)
156+
require.Equal(t, true, FileExists(dest))
157+
require.Equal(t, true, IsExecutable(dest))
158+
err = SetupExecFile(logging.NoLog{}, src, dest)
159+
require.NoError(t, err)
160+
content, err := os.ReadFile(dest)
161+
require.NoError(t, err)
162+
require.Equal(t, true, FileExists(dest))
163+
require.Equal(t, true, IsExecutable(dest))
164+
require.Equal(t, destContent, string(content))
165+
})
166+
}

0 commit comments

Comments
 (0)