Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/tarfs/tarfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ func New(ra io.ReaderAt, size int64) (*FS, error) {
return fsys, nil
}

func (fsys *FS) UnderlyingReader() io.ReaderAt {
return fsys.ra
}

func (fsys *FS) Close() error {
if fsys == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/apk/apk/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1281,7 +1281,7 @@ func (c *apkCache) get(ctx context.Context, a *APK, pkg InstallablePackage) (*ex
// If we find a value in the cache, we should check to make sure the tar file it references still exists.
// If it references a non-existent file, we should act as though this was a cache miss and expand the
// APK again.
if _, err := os.Stat(result.exp.TarFile); os.IsNotExist(err) {
if !result.exp.IsValid() {
newValue := sync.OnceValue(fn)
c.onces.Store(u, newValue)
result = newValue()
Expand Down
17 changes: 16 additions & 1 deletion pkg/apk/apk/implementation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,11 +756,13 @@ func TestFetchPackage(t *testing.T) {
t.Run("handle missing cache files when expanding APK", func(t *testing.T) {
tmpDir := t.TempDir()
a := prepLayout(t, tmpDir)

// Fill the cache
exp, err := a.expandPackage(ctx, pkg)
require.NoError(t, err, "unable to expand package")
_, err = os.Stat(exp.TarFile)
require.NoError(t, err, "unable to stat cached tar file")

// Delete the tar file from the cache
require.NoError(t, os.Remove(exp.TarFile), "unable to delete cached tar file")
_, err = os.Stat(exp.TarFile)
Expand All @@ -769,9 +771,22 @@ func TestFetchPackage(t *testing.T) {
// Expand the package again, this should re-populate the cache.
exp2, err := a.expandPackage(ctx, pkg)
require.NoError(t, err, "unable to expandPackage after deleting cached tar file")
_, err = os.Stat(exp2.TarFile)
require.NoError(t, err, "unable to stat cached tar file")

// Delete and recreate the tar file from the cache (changing its inodes)
bs, err := os.ReadFile(exp2.TarFile)
require.NoError(t, err, "unable to read cached tar file")
require.NoError(t, os.Remove(exp2.TarFile), "unable to delete cached tar file")
require.NoError(t, os.WriteFile(exp2.TarFile, bs, 0o644), "unable to recreate cached tar file")

// Ensure that the underlying reader is different (i.e. we re-read the file)
exp3, err := a.expandPackage(ctx, pkg)
require.NoError(t, err, "unable to expandPackage after deleting and recreating cached tar file")
require.NotEqual(t, exp2.TarFS.UnderlyingReader(), exp3.TarFS.UnderlyingReader())

// We should be able to read the APK contents
rc, err := exp2.APK()
rc, err := exp3.APK()
require.NoError(t, err, "unable to get reader for APK()")
_, err = io.ReadAll(rc)
require.NoError(t, err, "unable to read APK contents")
Expand Down
36 changes: 36 additions & 0 deletions pkg/apk/expandapk/expandapk.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,42 @@ func (m *multiReadCloser) Close() error {
return errors.Join(errs...)
}

// IsValid checks that the expanded APK is still valid by verifying that
// the underlying files exist and that the file handles match the expected files.
// Since this structure is heavily cached, this is useful to verify that the
// cached data is still valid.
func (a *APKExpanded) IsValid() bool {
if f, ok := a.TarFS.UnderlyingReader().(*os.File); ok {
// Verify that the file descriptor matches the expected file on disk.
fdInfo, err := f.Stat()
if err != nil {
return false
}

pathInfo, err := os.Stat(f.Name())
if err != nil {
return false
}

if !os.SameFile(fdInfo, pathInfo) {
return false
}
}

// Check that all the expected files exist.
files := []string{a.ControlFile, a.PackageFile, a.TarFile}
if a.SignatureFile != "" {
files = append(files, a.SignatureFile)
}
for _, file := range files {
if _, err := os.Stat(file); err != nil {
return false
}
}

return true
}

func (a *APKExpanded) Close() error {
errs := []error{}

Expand Down
Loading