From c6fa371d8d241f3e27b20ac23f502a3c11e7bd63 Mon Sep 17 00:00:00 2001 From: Elias-elastisys <112404905+Elias-elastisys@users.noreply.github.com> Date: Thu, 22 Jan 2026 03:43:32 +0100 Subject: [PATCH] Add symlink check to file rename util (#2576) --- pkg/yqlib/file_utils.go | 10 +++- pkg/yqlib/write_in_place_handler_test.go | 60 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/pkg/yqlib/file_utils.go b/pkg/yqlib/file_utils.go index 307e4591..a3f06698 100644 --- a/pkg/yqlib/file_utils.go +++ b/pkg/yqlib/file_utils.go @@ -7,7 +7,15 @@ import ( ) func tryRenameFile(from string, to string) error { - if renameError := os.Rename(from, to); renameError != nil { + if info, err := os.Lstat(to); err == nil && info.Mode()&os.ModeSymlink != 0 { + log.Debug("Target file is symlink, skipping rename and attempting to copy contents") + + if copyError := copyFileContents(from, to); copyError != nil { + return fmt.Errorf("failed copying from %v to %v: %w", from, to, copyError) + } + tryRemoveTempFile(from) + return nil + } else if renameError := os.Rename(from, to); renameError != nil { log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) log.Debug(renameError.Error()) log.Debug("going to try copying instead") diff --git a/pkg/yqlib/write_in_place_handler_test.go b/pkg/yqlib/write_in_place_handler_test.go index a15bdf32..1b33530b 100644 --- a/pkg/yqlib/write_in_place_handler_test.go +++ b/pkg/yqlib/write_in_place_handler_test.go @@ -139,6 +139,66 @@ func TestWriteInPlaceHandlerImpl_FinishWriteInPlace_Failure(t *testing.T) { } } +func TestWriteInPlaceHandlerImpl_FinishWriteInPlace_Symlink_Success(t *testing.T) { + // Create a temporary directory and file for testing + tempDir := t.TempDir() + inputFile := filepath.Join(tempDir, "input.yaml") + symlinkFile := filepath.Join(tempDir, "symlink.yaml") + + // Create input file with some content + content := []byte("test: value\n") + err := os.WriteFile(inputFile, content, 0600) + if err != nil { + t.Fatalf("Failed to create input file: %v", err) + } + + err = os.Symlink(inputFile, symlinkFile) + if err != nil { + t.Fatalf("Failed to symlink to input file: %v", err) + } + + handler := NewWriteInPlaceHandler(symlinkFile) + tempFile, err := handler.CreateTempFile() + if err != nil { + t.Fatalf("CreateTempFile failed: %v", err) + } + defer tempFile.Close() + + // Write some content to temp file + tempContent := []byte("updated: content\n") + _, err = tempFile.Write(tempContent) + if err != nil { + t.Fatalf("Failed to write to temp file: %v", err) + } + tempFile.Close() + + // Test successful finish + err = handler.FinishWriteInPlace(true) + if err != nil { + t.Fatalf("FinishWriteInPlace failed: %v", err) + } + + // Verify that the symlink is still present + info, err := os.Lstat(symlinkFile) + if err != nil { + t.Fatalf("Failed to lstat input file: %v", err) + } + if info.Mode()&os.ModeSymlink == 0 { + t.Errorf("Input file symlink is no longer present") + } + + // Verify the original file was updated + updatedContent, err := os.ReadFile(inputFile) + if err != nil { + t.Fatalf("Failed to read updated file: %v", err) + } + + if string(updatedContent) != string(tempContent) { + t.Errorf("File content not updated correctly. Expected %q, got %q", + string(tempContent), string(updatedContent)) + } +} + func TestWriteInPlaceHandlerImpl_CreateTempFile_Permissions(t *testing.T) { // Create a temporary directory and file for testing tempDir := t.TempDir()