check / check (push) Waiting to run
List and ListStream walked the destination with afero.Walk, which uses Lstat and so reported a symlinked metadata/ or blobs/ directory as one file, while Put, Get and Stat follow the link. The store then listed as empty, and prune removed the local snapshot records and deleted every blob. Both listings now walk a view of the filesystem without its Lstat method, so afero falls back to Stat and enters the link. A symlink loop is not detected: it is followed until the system refuses the path for too many levels of links, and the listing fails. Model: opus-5-5
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// TestPruneDeletesNothingThroughSymlinkedDirectory checks that prune keeps
|
|
// every blob and the local snapshot record when the metadata/ or blobs/
|
|
// directory of a file:// destination is a symlink to a directory elsewhere
|
|
// (https://git.eeqj.de/sneak/vaultik/issues/296). A backup writes through
|
|
// such a link, so prune has to list through it too.
|
|
func TestPruneDeletesNothingThroughSymlinkedDirectory(t *testing.T) {
|
|
log.Initialize(log.Config{})
|
|
t.Parallel()
|
|
|
|
for _, dir := range []string{"metadata", "blobs"} {
|
|
t.Run(dir, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
storeDir := filepath.Join(t.TempDir(), "store")
|
|
v, repos, _ := backUpToFileDestination(ctx, t, storeDir)
|
|
|
|
var blobFiles []string
|
|
|
|
err := filepath.WalkDir(filepath.Join(storeDir, "blobs"),
|
|
func(path string, entry fs.DirEntry, err error) error {
|
|
if err == nil && !entry.IsDir() {
|
|
blobFiles = append(blobFiles, path)
|
|
}
|
|
|
|
return err
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, blobFiles)
|
|
|
|
linkPath := filepath.Join(storeDir, dir)
|
|
movedPath := filepath.Join(t.TempDir(), dir)
|
|
require.NoError(t, os.Rename(linkPath, movedPath))
|
|
require.NoError(t, os.Symlink(movedPath, linkPath))
|
|
|
|
require.NoError(t, v.Prune(&vaultik.PruneOptions{Force: true}))
|
|
|
|
// When blobs/ is the link, these paths run through it.
|
|
for _, blobFile := range blobFiles {
|
|
assert.FileExists(t, blobFile, "prune must delete no blob")
|
|
}
|
|
|
|
snapshots, err := repos.Snapshots.ListRecent(ctx, listRecentTestLimit)
|
|
require.NoError(t, err)
|
|
assert.Len(t, snapshots, 1, "prune must delete no local snapshot record")
|
|
})
|
|
}
|
|
}
|