Delete .tmp- leftovers of a killed command when the lock is next taken (closes #75)
check / check (push) Failing after 2s

A command killed part-way could leave a temporary file or directory of
secret.WriteFileAtomic or secret.TempDirFor, encrypted keys included,
for good. LockStateDir now empties the lock file once it holds the lock
and writes "finished" there just before releasing it. A holder that
does not find that deletes such leftovers from the state directory,
each vault, each secret and each version, the only places those helpers
make them, matching names that start with "." and hold ".tmp-". After a
command that finished nothing is searched, so the added time does not
grow with the number of secrets and versions. A test shows that
`unlocker remove` removes an unlocker directory with no metadata file.

Model: opus-5-5
This commit was merged in pull request #101.
This commit is contained in:
2026-10-04 19:25:25 +02:00
parent 1d7f78fd0d
commit 015730fb05
8 changed files with 317 additions and 17 deletions
+47
View File
@@ -1,9 +1,11 @@
package vault_test
import (
"path/filepath"
"testing"
"time"
"git.eeqj.de/sneak/secret/internal/secret"
"git.eeqj.de/sneak/secret/internal/vault"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@@ -121,6 +123,51 @@ func TestLockStateDirFreeAfterPanic(t *testing.T) {
}
}
// TestLockStateDirRemovesLeftoversOnlyAfterKill checks that taking the lock
// deletes a temporary directory a killed command left only when the last
// holder of the lock did not release it. A holder killed while it holds the
// lock leaves the lock file as it is at that moment.
func TestLockStateDirRemovesLeftoversOnlyAfterKill(t *testing.T) {
t.Parallel()
for _, lfs := range lockFilesystems(t) {
t.Run(lfs.name, func(t *testing.T) {
t.Parallel()
lockFile := filepath.Join(lfs.stateDir, "lock")
leftover := filepath.Join(lfs.stateDir, ".tmp-1")
release, err := vault.LockStateDir(lfs.fs, lfs.stateDir)
require.NoError(t, err)
whileHeld, err := afero.ReadFile(lfs.fs, lockFile)
require.NoError(t, err)
release()
require.NoError(t, lfs.fs.MkdirAll(leftover, secret.DirPerms))
release, err = vault.LockStateDir(lfs.fs, lfs.stateDir)
require.NoError(t, err)
release()
exists, err := afero.DirExists(lfs.fs, leftover)
require.NoError(t, err)
assert.True(t, exists, "searched after a holder that finished")
require.NoError(t, afero.WriteFile(lfs.fs, lockFile, whileHeld,
secret.FilePerms))
release, err = vault.LockStateDir(lfs.fs, lfs.stateDir)
require.NoError(t, err)
release()
exists, err = afero.DirExists(lfs.fs, leftover)
require.NoError(t, err)
assert.False(t, exists, "not searched after a holder that was killed")
})
}
}
// TestLockStateDirRefusesOtherFilesystems checks that a filesystem with no
// lock implementation is refused instead of being used unlocked.
func TestLockStateDirRefusesOtherFilesystems(t *testing.T) {