Replace symlinks with plain files containing relative paths
- Remove all symlink creation and resolution in favor of plain files - currentvault file now contains relative path like "vaults.d/default" - current-unlocker file now contains relative path like "unlockers.d/passphrase" - current version file now contains relative path like "versions/20231215.001" - Simplify path resolution to just read file contents and join with parent dir - Update all tests to read files instead of using os.Readlink
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.eeqj.de/sneak/secret/internal/secret"
|
||||
@@ -31,104 +32,31 @@ func isValidVaultName(name string) bool {
|
||||
return matched
|
||||
}
|
||||
|
||||
// resolveRelativeSymlink resolves a relative symlink target to an absolute path
|
||||
func resolveRelativeSymlink(symlinkPath, _ string) (string, error) {
|
||||
// Get the current directory before changing
|
||||
originalDir, err := os.Getwd()
|
||||
// ResolveVaultSymlink reads the currentvault file to get the path to the current vault
|
||||
// The file contains a relative path to the vault directory
|
||||
func ResolveVaultSymlink(fs afero.Fs, currentVaultPath string) (string, error) {
|
||||
secret.Debug("resolveVaultSymlink starting", "path", currentVaultPath)
|
||||
|
||||
fileData, err := afero.ReadFile(fs, currentVaultPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get current directory: %w", err)
|
||||
}
|
||||
secret.Debug("Got current directory", "original_dir", originalDir)
|
||||
secret.Debug("Failed to read currentvault file", "error", err)
|
||||
|
||||
// Change to the symlink's directory
|
||||
symlinkDir := filepath.Dir(symlinkPath)
|
||||
secret.Debug("Changing to symlink directory", "symlink_path", symlinkDir)
|
||||
secret.Debug("About to call os.Chdir - this might hang if symlink is broken")
|
||||
if err := os.Chdir(symlinkDir); err != nil {
|
||||
return "", fmt.Errorf("failed to change to symlink directory: %w", err)
|
||||
return "", fmt.Errorf("failed to read currentvault file: %w", err)
|
||||
}
|
||||
secret.Debug("Changed to symlink directory successfully - os.Chdir completed")
|
||||
|
||||
// Get the absolute path of the target
|
||||
secret.Debug("Getting absolute path of current directory")
|
||||
absolutePath, err := os.Getwd()
|
||||
if err != nil {
|
||||
// Try to restore original directory before returning error
|
||||
_ = os.Chdir(originalDir)
|
||||
// The file contains a relative path like "vaults.d/default"
|
||||
relativePath := strings.TrimSpace(string(fileData))
|
||||
secret.Debug("Read relative path from file", "relative_path", relativePath)
|
||||
|
||||
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
||||
}
|
||||
secret.Debug("Got absolute path", "absolute_path", absolutePath)
|
||||
// Resolve to absolute path relative to the state directory
|
||||
stateDir := filepath.Dir(currentVaultPath)
|
||||
absolutePath := filepath.Join(stateDir, relativePath)
|
||||
|
||||
// Restore the original directory
|
||||
secret.Debug("Restoring original directory", "original_dir", originalDir)
|
||||
if err := os.Chdir(originalDir); err != nil {
|
||||
return "", fmt.Errorf("failed to restore original directory: %w", err)
|
||||
}
|
||||
secret.Debug("Restored original directory successfully")
|
||||
secret.Debug("Resolved to absolute path", "absolute_path", absolutePath)
|
||||
|
||||
return absolutePath, nil
|
||||
}
|
||||
|
||||
// ResolveVaultSymlink resolves the currentvault symlink by reading either the symlink target or file contents
|
||||
// This function is designed to work on both Unix and Windows systems, as well as with in-memory filesystems
|
||||
func ResolveVaultSymlink(fs afero.Fs, symlinkPath string) (string, error) {
|
||||
secret.Debug("resolveVaultSymlink starting", "symlink_path", symlinkPath)
|
||||
|
||||
// First try to handle the path as a real symlink (works on Unix systems)
|
||||
_, isOsFs := fs.(*afero.OsFs)
|
||||
if isOsFs {
|
||||
target, err := tryResolveOsSymlink(symlinkPath)
|
||||
if err == nil {
|
||||
secret.Debug("resolveVaultSymlink completed successfully", "result", target)
|
||||
|
||||
return target, nil
|
||||
}
|
||||
// Fall through to fallback if symlink resolution failed
|
||||
} else {
|
||||
secret.Debug("Not using OS filesystem, skipping symlink resolution")
|
||||
}
|
||||
|
||||
// Fallback: treat it as a regular file containing the target path
|
||||
secret.Debug("Fallback: trying to read regular file with target path")
|
||||
|
||||
fileData, err := afero.ReadFile(fs, symlinkPath)
|
||||
if err != nil {
|
||||
secret.Debug("Failed to read target path file", "error", err)
|
||||
|
||||
return "", fmt.Errorf("failed to read vault symlink: %w", err)
|
||||
}
|
||||
|
||||
target := string(fileData)
|
||||
secret.Debug("Read target path from file", "target", target)
|
||||
|
||||
secret.Debug("resolveVaultSymlink completed via fallback", "result", target)
|
||||
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// tryResolveOsSymlink attempts to resolve a symlink on OS filesystems
|
||||
func tryResolveOsSymlink(symlinkPath string) (string, error) {
|
||||
secret.Debug("Using real filesystem symlink resolution")
|
||||
|
||||
// Check if the symlink exists
|
||||
secret.Debug("Checking symlink target", "symlink_path", symlinkPath)
|
||||
target, err := os.Readlink(symlinkPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
secret.Debug("Symlink points to", "target", target)
|
||||
|
||||
// On real filesystem, we need to handle relative symlinks
|
||||
// by resolving them relative to the symlink's directory
|
||||
if !filepath.IsAbs(target) {
|
||||
return resolveRelativeSymlink(symlinkPath, target)
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
|
||||
// GetCurrentVault gets the current vault from the file system
|
||||
func GetCurrentVault(fs afero.Fs, stateDir string) (*Vault, error) {
|
||||
secret.Debug("Getting current vault", "state_dir", stateDir)
|
||||
@@ -328,32 +256,19 @@ func SelectVault(fs afero.Fs, stateDir string, name string) error {
|
||||
return fmt.Errorf("vault %s does not exist", name)
|
||||
}
|
||||
|
||||
// Create or update the current vault symlink/file
|
||||
// Create or update the currentvault file with the relative path
|
||||
currentVaultPath := filepath.Join(stateDir, "currentvault")
|
||||
targetPath := filepath.Join(stateDir, "vaults.d", name)
|
||||
relativePath := filepath.Join("vaults.d", name)
|
||||
|
||||
// First try to remove existing symlink if it exists
|
||||
// Remove existing file if it exists
|
||||
if _, err := fs.Stat(currentVaultPath); err == nil {
|
||||
secret.Debug("Removing existing current vault symlink", "path", currentVaultPath)
|
||||
// Ignore errors from Remove as we'll try to create/update it anyway.
|
||||
// On some systems, removing a symlink may fail but the subsequent create may still succeed.
|
||||
secret.Debug("Removing existing currentvault file", "path", currentVaultPath)
|
||||
_ = fs.Remove(currentVaultPath)
|
||||
}
|
||||
|
||||
// Try to create a real symlink first (works on Unix systems)
|
||||
if _, ok := fs.(*afero.OsFs); ok {
|
||||
secret.Debug("Creating vault symlink", "target", targetPath, "link", currentVaultPath)
|
||||
if err := os.Symlink(targetPath, currentVaultPath); err == nil {
|
||||
secret.Debug("Successfully selected vault", "vault_name", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
// If symlink creation fails, fall back to regular file
|
||||
}
|
||||
|
||||
// Fallback: create a regular file with the target path
|
||||
secret.Debug("Fallback: creating regular file with target path", "target", targetPath)
|
||||
if err := afero.WriteFile(fs, currentVaultPath, []byte(targetPath), secret.FilePerms); err != nil {
|
||||
// Write the relative path to the file
|
||||
secret.Debug("Writing currentvault file", "relative_path", relativePath)
|
||||
if err := afero.WriteFile(fs, currentVaultPath, []byte(relativePath), secret.FilePerms); err != nil {
|
||||
return fmt.Errorf("failed to select vault: %w", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user