From 34e454870aa2cc53fa37c82c92dc24d0cf133b01 Mon Sep 17 00:00:00 2001 From: SA6MWA Michel Date: Tue, 21 Mar 2023 20:52:55 +0100 Subject: [PATCH] Recreate lockfile on close and remove lockfile with new interface Close func --- .gitignore | 8 +++ anystore.go | 121 ++++++++++++++++++++++++++++------ examples/edit-stash-2/main.go | 1 + stash.go | 2 + 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1f60ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* diff --git a/anystore.go b/anystore.go index ca4ab8f..69eae88 100644 --- a/anystore.go +++ b/anystore.go @@ -28,6 +28,7 @@ Example: if err != nil { log.Fatal(err) } + defer persisted.Close() if err := ephemeral.Store("hello", "world"); err != nil { log.Fatal(err) @@ -57,12 +58,10 @@ Example: } log.Println(v) - } - -AnyStore also feature a configuration mode with convenience-functions Stash, -Unstash and EditThing. Whether you choose to hard-code an encryption key in the -application or provide one via environment variables, using Stash, Unstash and -EditThing is simple... +AnyStore also feature a configuration mode with convenience-functions +Stash, Unstash and EditThing. Whether you choose to hard-code an +encryption key in the application or provide one via environment +variables, using Stash, Unstash and EditThing is simple... package main @@ -217,6 +216,9 @@ type AnyStore interface { // function is returned by Run. Run(atomicOperation func(s AnyStore) error) error + // If persistence is enabled, Close removes the lockfile. + Close() error + load() error loadStoreAndSave(key any, value any, remove bool) error @@ -450,6 +452,30 @@ func (a *anyStore) Run(atomicOperation func(s AnyStore) error) error { return atomicOperation(anyStoreOverride) } +func (a *anyStore) Close() error { + if a.persist.Load() { + // Lock the store + a.mutex.Lock() + defer a.mutex.Unlock() + file, ok := a.savefile.Load().(string) + if !ok { + return errors.New("persistence not set") + } + lockfile := file + ".lock" + _, err := os.Stat(lockfile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + if err := os.Remove(lockfile); err != nil { + return err + } + } + return nil +} + func (a *anyStore) load() error { file, ok := a.savefile.Load().(string) if !ok { @@ -521,13 +547,30 @@ func (a *anyStore) loadStoreAndSave(key any, value any, remove bool) error { } lockfile := file + ".lock" unlink := true - lockfd, err := syscall.Open(lockfile, syscall.O_CREAT|syscall.O_RDWR, 0666) - if err != nil { - return err - } - defer syscall.Close(lockfd) - if err := syscall.Flock(lockfd, syscall.LOCK_EX); err != nil { - return err + var lockfd int + for { + var err error + lockfd, err = syscall.Open(lockfile, syscall.O_CREAT|syscall.O_RDWR, 0666) + if err != nil { + return err + } + if err := syscall.Flock(lockfd, syscall.LOCK_EX); err != nil { + syscall.Close(lockfd) + return err + } + var stat_t syscall.Stat_t + if err := syscall.Fstat(lockfd, &stat_t); err != nil { + syscall.Close(lockfd) + return err + } + if stat_t.Nlink == 0 { + // File deleted (no hard links), recreate it + syscall.Close(lockfd) + continue + } + // We should have a lockfd with an existing file at this point + defer syscall.Close(lockfd) + break } f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666) if err != nil { @@ -767,6 +810,27 @@ func (u *unsafeAnyStore) Run(atomicOperation func(s AnyStore) error) error { return atomicOperation(u) } +func (u *unsafeAnyStore) Close() error { + if u.persist.Load() { + file, ok := u.savefile.Load().(string) + if !ok { + return errors.New("persistence not set") + } + lockfile := file + ".lock" + _, err := os.Stat(lockfile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + if err := os.Remove(lockfile); err != nil { + return err + } + } + return nil +} + func (u *unsafeAnyStore) load() error { file, ok := u.savefile.Load().(string) if !ok { @@ -838,13 +902,30 @@ func (u *unsafeAnyStore) loadStoreAndSave(key any, value any, remove bool) error } lockfile := file + ".lock" unlink := true - lockfd, err := syscall.Open(lockfile, syscall.O_CREAT|syscall.O_RDWR, 0666) - if err != nil { - return err - } - defer syscall.Close(lockfd) - if err := syscall.Flock(lockfd, syscall.LOCK_EX); err != nil { - return err + var lockfd int + for { + var err error + lockfd, err = syscall.Open(lockfile, syscall.O_CREAT|syscall.O_RDWR, 0666) + if err != nil { + return err + } + if err := syscall.Flock(lockfd, syscall.LOCK_EX); err != nil { + syscall.Close(lockfd) + return err + } + var stat_t syscall.Stat_t + if err := syscall.Fstat(lockfd, &stat_t); err != nil { + syscall.Close(lockfd) + return err + } + if stat_t.Nlink == 0 { + // File deleted (no hard links), recreate it + syscall.Close(lockfd) + continue + } + // We should have a lockfd with an existing file at this point + defer syscall.Close(lockfd) + break } f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666) if err != nil { diff --git a/examples/edit-stash-2/main.go b/examples/edit-stash-2/main.go index b758dd1..01e74a2 100644 --- a/examples/edit-stash-2/main.go +++ b/examples/edit-stash-2/main.go @@ -58,6 +58,7 @@ func main() { }); err != nil { log.Fatal(err) } + fmt.Println("Saved configuration as ", file) } j, err := json.MarshalIndent(configuration, "", " ") diff --git a/stash.go b/stash.go index b36f064..dba9bfe 100644 --- a/stash.go +++ b/stash.go @@ -84,6 +84,7 @@ func Unstash(conf *StashConfig) error { if err != nil { return err } + defer a.Close() var gobbedThing any if conf.Reader != nil { // Read encrypted anyMap @@ -202,6 +203,7 @@ func Stash(conf *StashConfig) error { if err != nil { return err } + defer a.Close() // Use gob to store the struct (or other value) instead of re-inventing // dereference of all pointers. It is also unlikely that the interface stored