Files
aptly/cmd/db_recover.go
Agustin Henze 785f058613 Fix db recover when repo.RefList is nil
It tries to find dangling references but repo has no reference list and
it ended up with a nice trace
```
root@hostname:/usr/scratch/agustin/aptly-stress-tester# aptly db recover
Recovering database...
Checking database integrity...
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x13b9c24]

goroutine 1 [running]:
github.com/aptly-dev/aptly/cmd.Run.func1()
        /home/tin/projects/aptly/cmd/run.go:17 +0xc5
panic({0x15abf60?, 0x2da1650?})
        /usr/local/go/src/runtime/panic.go:792 +0x132
github.com/aptly-dev/aptly/deb.(*PackageRefList).ForEach(...)
        /home/tin/projects/aptly/deb/reflist.go:83
github.com/aptly-dev/aptly/deb.FindDanglingReferences(...)
        /home/tin/projects/aptly/deb/find_dangling.go:15
github.com/aptly-dev/aptly/cmd.checkRepo(0xc0015827e0)
        /home/tin/projects/aptly/cmd/db_recover.go:63 +0xa4
github.com/aptly-dev/aptly/cmd.checkIntegrity.(*LocalRepoCollection).ForEach.func1({0xc0002fc2d0?, 0xc0010c2018?, 0x1?}, {0xc11004c000, 0xb9, 0xe0})
        /home/tin/projects/aptly/deb/local.go:229 +0xa7
github.com/aptly-dev/aptly/database/goleveldb.(*storage).ProcessByPrefix(0xc0005ae140?, {0xc0010c2018, 0x1, 0x1}, 0xc00107be40)
        /home/tin/projects/aptly/database/goleveldb/storage.go:114 +0x184
github.com/aptly-dev/aptly/deb.(*LocalRepoCollection).ForEach(...)
        /home/tin/projects/aptly/deb/local.go:222
github.com/aptly-dev/aptly/cmd.checkIntegrity()
        /home/tin/projects/aptly/cmd/db_recover.go:51 +0x7f
github.com/aptly-dev/aptly/cmd.aptlyDBRecover(0x40ed4e?, {0xc0003b5c30?, 0x2?, 0xc00057bdb0?})
        /home/tin/projects/aptly/cmd/db_recover.go:27 +0x93
github.com/smira/commander.(*Command).Dispatch(0xc00052c900, {0xc0003b5c30, 0x0, 0x0})
        /go/pkg/mod/github.com/smira/commander@v0.0.0-20140515201010-f408b00e68d5/commands.go:305 +0xd1
github.com/smira/commander.(*Command).Dispatch(0xc00052ca20, {0xc0003b5c30, 0x1, 0x1})
        /go/pkg/mod/github.com/smira/commander@v0.0.0-20140515201010-f408b00e68d5/commands.go:283 +0x165
github.com/smira/commander.(*Command).Dispatch(0xc000516000, {0xc0003b5c20, 0x2, 0x2})
        /go/pkg/mod/github.com/smira/commander@v0.0.0-20140515201010-f408b00e68d5/commands.go:283 +0x165
github.com/aptly-dev/aptly/cmd.Run(0xc000516000, {0xc000194280?, 0x0?, 0x24a5bc8?}, 0x1)
        /home/tin/projects/aptly/cmd/run.go:41 +0x1b9
main.main()
        /home/tin/projects/aptly/main.go:27 +0x10d
```

After applying the fix you get
```
root@hostname:~/aptly-stress-tester# aptly db recover
Recovering database...
Checking database integrity...
Warning: Repo "stress_test_repo_3532" has no reference list (severely corrupted), initializing empty list
Warning: Repo "stress_test_repo_9244" has no reference list (severely corrupted), initializing empty list
```

Also it tries better to avoid nil references in repos, and snapshots
2026-01-04 13:40:01 +01:00

99 lines
2.4 KiB
Go

package cmd
import (
"fmt"
"github.com/aptly-dev/aptly/deb"
"github.com/smira/commander"
"github.com/aptly-dev/aptly/database/goleveldb"
)
// aptly db recover
func aptlyDBRecover(cmd *commander.Command, args []string) error {
var err error
if len(args) != 0 {
cmd.Usage()
return commander.ErrCommandError
}
context.Progress().Printf("Recovering database...\n")
if err = goleveldb.RecoverDB(context.DBPath()); err != nil {
return err
}
context.Progress().Printf("Checking database integrity...\n")
err = checkIntegrity()
return err
}
func makeCmdDBRecover() *commander.Command {
cmd := &commander.Command{
Run: aptlyDBRecover,
UsageLine: "recover",
Short: "recover DB after crash",
Long: `
Database recover does its' best to recover the database after a crash.
It is recommended to backup the DB before running recover.
Example:
$ aptly db recover
`,
}
return cmd
}
func checkIntegrity() error {
return context.NewCollectionFactory().LocalRepoCollection().ForEach(checkRepo)
}
func checkRepo(repo *deb.LocalRepo) error {
collectionFactory := context.NewCollectionFactory()
repos := collectionFactory.LocalRepoCollection()
err := repos.LoadComplete(repo)
if err != nil {
// If we can't load the repo, it might be severely corrupted
// Log the error but continue with other repos
context.Progress().Printf("Warning: Cannot load repo %q: %s\n", repo.Name, err)
return nil
}
// Check if RefList is nil (severe corruption case)
refList := repo.RefList()
if refList == nil {
context.Progress().Printf("Warning: Repo %q has no reference list (severely corrupted), initializing empty list\n", repo.Name)
// Initialize with empty reflist
repo.UpdateRefList(deb.NewPackageRefList())
if err = repos.Update(repo); err != nil {
return fmt.Errorf("update repo with empty reflist: %w", err)
}
return nil
}
dangling, err := deb.FindDanglingReferences(refList, collectionFactory.PackageCollection())
if err != nil {
// If we can't find dangling references, log but continue
context.Progress().Printf("Warning: Cannot check dangling references for repo %q: %s\n", repo.Name, err)
return nil
}
if dangling != nil && len(dangling.Refs) > 0 {
for _, ref := range dangling.Refs {
context.Progress().Printf("Removing dangling database reference %q\n", ref)
}
repo.UpdateRefList(refList.Subtract(dangling))
if err = repos.Update(repo); err != nil {
return fmt.Errorf("update repo: %w", err)
}
}
return nil
}