diff --git a/agent/app/service/file.go b/agent/app/service/file.go index 4eada4eb2..fb92569b7 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -882,6 +882,16 @@ func (f *FileService) ChangeName(req request.FileRename) error { if hasInvalidFileName(req.NewName) { return buserr.New("ErrInvalidChar") } + if req.OldName == req.NewName { + return nil + } + // os.Rename silently replaces an existing target on Linux, so reject the + // rename here to avoid losing the target file without confirmation. + if _, err := os.Lstat(req.NewName); err == nil { + return buserr.New("ErrFileIsExist") + } else if !os.IsNotExist(err) { + return err + } fo := files.NewFileOp() content, mode, shouldRecordHistory := readEditableFileHistoryContent(req.OldName) if err := fo.Rename(req.OldName, req.NewName); err != nil { diff --git a/agent/app/service/file_test.go b/agent/app/service/file_test.go new file mode 100644 index 000000000..e35bea40f --- /dev/null +++ b/agent/app/service/file_test.go @@ -0,0 +1,63 @@ +package service + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/buserr" +) + +func TestChangeNameRejectsExistingTarget(t *testing.T) { + dir := t.TempDir() + oldPath := filepath.Join(dir, "old.txt") + targetPath := filepath.Join(dir, "target.txt") + // Binary source content keeps this test independent of the file-history DB. + if err := os.WriteFile(oldPath, []byte("old\x00"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(targetPath, []byte("target"), 0644); err != nil { + t.Fatal(err) + } + + err := (&FileService{}).ChangeName(request.FileRename{OldName: oldPath, NewName: targetPath}) + if err == nil { + t.Fatal("expected an error when the target name already exists") + } + var bizErr buserr.BusinessError + if !errors.As(err, &bizErr) || bizErr.Msg != "ErrFileIsExist" { + t.Fatalf("expected ErrFileIsExist, got %v", err) + } + + content, err := os.ReadFile(targetPath) + if err != nil { + t.Fatal(err) + } + if string(content) != "target" { + t.Fatalf("target file was overwritten, content = %q", content) + } + if _, err := os.Stat(oldPath); err != nil { + t.Fatalf("source file should still exist after a rejected rename: %v", err) + } +} + +func TestChangeNameRenamesToNewName(t *testing.T) { + dir := t.TempDir() + oldPath := filepath.Join(dir, "old") + newPath := filepath.Join(dir, "new") + if err := os.Mkdir(oldPath, 0755); err != nil { + t.Fatal(err) + } + + if err := (&FileService{}).ChangeName(request.FileRename{OldName: oldPath, NewName: newPath}); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(newPath); err != nil { + t.Fatalf("renamed path does not exist: %v", err) + } + if _, err := os.Stat(oldPath); !os.IsNotExist(err) { + t.Fatalf("old path should be gone, stat err = %v", err) + } +}