diff --git a/client/client_test.go b/client/client_test.go index 6ad92d4..39b456a 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -12,6 +12,7 @@ import ( "runtime" "strconv" "strings" + "sync" "testing" "time" @@ -2290,6 +2291,43 @@ echo foo`) compareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery") } +func TestSetConfigNoCorruption(t *testing.T) { + // Setup + tester := zshTester{} + defer testutils.BackupAndRestore(t)() + installHishtory(t, tester, "") + + // A test that tries writing a config many different times in parallel, and confirms there is no corruption + conf, err := hctx.GetConfig() + testutils.Check(t, err) + var doneWg sync.WaitGroup + for i := 0; i < 10; i++ { + doneWg.Add(1) + go func(i int) { + // Make a new config of a varied length + c := conf + c.LastSavedHistoryLine = strings.Repeat("A", i) + c.DeviceId = strings.Repeat("B", i*2) + c.HaveMissedUploads = (i % 2) == 0 + // Write it + err := hctx.SetConfig(c) + if err != nil { + panic(err) + } + // Check that we can read + c2, err := hctx.GetConfig() + if err != nil { + panic(err) + } + if c2.UserSecret != c.UserSecret { + panic("user secret mismatch") + } + doneWg.Done() + }(i) + } + doneWg.Wait() +} + type deviceSet struct { deviceMap *map[device]deviceOp currentDevice *device diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index ad8fa80..667ae9a 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -11,6 +11,7 @@ import ( "time" "github.com/ddworken/hishtory/client/data" + "github.com/google/uuid" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" "gorm.io/gorm" @@ -237,7 +238,7 @@ func SetConfig(config ClientConfig) error { return err } configPath := path.Join(homedir, data.HISHTORY_PATH, data.CONFIG_PATH) - stagedConfigPath := configPath + ".tmp" + stagedConfigPath := configPath + ".tmp-" + uuid.Must(uuid.NewRandom()).String() err = os.WriteFile(stagedConfigPath, serializedConfig, 0o644) if err != nil { return fmt.Errorf("failed to write config: %v", err)