diff --git a/lnutils/fs.go b/lnutils/fs.go new file mode 100644 index 000000000..bc09176cc --- /dev/null +++ b/lnutils/fs.go @@ -0,0 +1,31 @@ +package lnutils + +import ( + "errors" + "fmt" + "os" +) + +// CreateDir creates a directory if it doesn't exist and also handles +// symlink-related errors with user-friendly messages. It creates all necessary +// parent directories with the specified permissions. +func CreateDir(dir string, perm os.FileMode) error { + err := os.MkdirAll(dir, perm) + if err == nil { + return nil + } + + // Show a nicer error message if it's because a symlink + // is linked to a directory that does not exist + // (probably because it's not mounted). + var pathErr *os.PathError + if errors.As(err, &pathErr) && os.IsExist(err) { + link, lerr := os.Readlink(pathErr.Path) + if lerr == nil { + return fmt.Errorf("is symlink %s -> %s "+ + "mounted?", pathErr.Path, link) + } + } + + return fmt.Errorf("failed to create directory '%s': %w", dir, err) +} diff --git a/lnutils/fs_test.go b/lnutils/fs_test.go new file mode 100644 index 000000000..3e96d4faf --- /dev/null +++ b/lnutils/fs_test.go @@ -0,0 +1,87 @@ +package lnutils + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestCreateDir verifies the behavior of CreateDir function in various +// scenarios: +// - Creating a new directory when it doesn't exist +// - Handling an already existing directory +// - Dealing with symlinks pointing to non-existent directories +// - Handling invalid paths +// The test uses a temporary directory and runs multiple test cases to ensure +// proper directory creation, permission settings, and error handling. +func TestCreateDir(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + + tests := []struct { + name string + setup func() string + wantError bool + }{ + { + name: "create directory", + setup: func() string { + return filepath.Join(tempDir, "testdir") + }, + wantError: false, + }, + { + name: "existing directory", + setup: func() string { + dir := filepath.Join(tempDir, "testdir") + err := os.Mkdir(dir, 0700) + require.NoError(t, err) + + return dir + }, + wantError: false, + }, + { + name: "symlink to non-existent directory", + setup: func() string { + dir := filepath.Join(tempDir, "testdir") + symlink := filepath.Join(tempDir, "symlink") + err := os.Symlink(dir, symlink) + require.NoError(t, err) + + return symlink + }, + wantError: true, + }, + { + name: "invalid path", + setup: func() string { + return string([]byte{0}) + }, + wantError: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + dir := tc.setup() + defer os.RemoveAll(dir) + + err := CreateDir(dir, 0700) + if tc.wantError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + info, err := os.Stat(dir) + require.NoError(t, err) + require.True(t, info.IsDir()) + }) + } +}