mirror of
https://github.com/wavetermdev/waveterm
synced 2026-05-01 03:17:16 +00:00
`WriteWaveHomeConfigFile()` previously used direct `os.WriteFile`, which
can expose truncation/partial-write states to the JSON file watcher.
This change switches config persistence to temp-file + rename semantics
and serializes writes through a single process-wide lock for config file
writes.
- **Atomic file write helper**
- Added `AtomicWriteFile()` in `pkg/util/fileutil/fileutil.go`.
- Writes to `<filename>.tmp` in the same directory, then renames to the
target path.
- Performs temp-file cleanup on error paths.
- Introduced a shared suffix constant (`TempFileSuffix`) used by
implementation/tests.
- **Config write path update**
- Updated `WriteWaveHomeConfigFile()` in `pkg/wconfig/settingsconfig.go`
to:
- Use a package-level mutex (`configWriteLock`) so only one config write
runs at a time (across all config files).
- Call `fileutil.AtomicWriteFile(...)` instead of direct
`os.WriteFile(...)`.
- **Focused coverage for atomic behavior**
- Added `pkg/util/fileutil/fileutil_test.go` with tests for:
- Successful atomic write (target file contains expected payload and no
leftover `.tmp` file).
- Rename-failure path cleanup (temp file is removed).
```go
func WriteWaveHomeConfigFile(fileName string, m waveobj.MetaMapType) error {
configWriteLock.Lock()
defer configWriteLock.Unlock()
fullFileName := filepath.Join(wavebase.GetWaveConfigDir(), fileName)
barr, err := jsonMarshalConfigInOrder(m)
if err != nil {
return err
}
return fileutil.AtomicWriteFile(fullFileName, barr, 0644)
}
```
<!-- START COPILOT CODING AGENT TIPS -->
---
🔒 GitHub Advanced Security automatically protects Copilot coding agent
pull requests. You can protect all pull requests by enabling Advanced
Security for your repositories. [Learn more about Advanced
Security.](https://gh.io/cca-advanced-security)
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>
45 lines
1.2 KiB
Go
45 lines
1.2 KiB
Go
package fileutil
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestAtomicWriteFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
fileName := filepath.Join(tmpDir, "settings.json")
|
|
|
|
err := AtomicWriteFile(fileName, []byte(`{"key":"value"}`), 0644)
|
|
if err != nil {
|
|
t.Fatalf("AtomicWriteFile failed: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(fileName)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile failed: %v", err)
|
|
}
|
|
if string(data) != `{"key":"value"}` {
|
|
t.Fatalf("unexpected file contents: %q", string(data))
|
|
}
|
|
if _, err := os.Stat(fileName + TempFileSuffix); !os.IsNotExist(err) {
|
|
t.Fatalf("temporary file should not exist, stat err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAtomicWriteFileRenameErrorCleansTempFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
fileName := filepath.Join(tmpDir, "settings.json")
|
|
|
|
if err := os.Mkdir(fileName, 0755); err != nil {
|
|
t.Fatalf("Mkdir failed: %v", err)
|
|
}
|
|
|
|
err := AtomicWriteFile(fileName, []byte(`{"key":"value"}`), 0644)
|
|
if err == nil {
|
|
t.Fatalf("AtomicWriteFile expected error")
|
|
}
|
|
if _, statErr := os.Stat(fileName + TempFileSuffix); !os.IsNotExist(statErr) {
|
|
t.Fatalf("temporary file should be removed on rename error, stat err: %v", statErr)
|
|
}
|
|
}
|