diff --git a/cmd/bbolt/command_surgery_cobra.go b/cmd/bbolt/command_surgery_cobra.go index 407d7ec..cb7f895 100644 --- a/cmd/bbolt/command_surgery_cobra.go +++ b/cmd/bbolt/command_surgery_cobra.go @@ -19,14 +19,15 @@ var ( ) func newSurgeryCobraCommand() *cobra.Command { - cmd := &cobra.Command{ + surgeryCmd := &cobra.Command{ Use: "surgery ", Short: "surgery related commands", } - cmd.AddCommand(newSurgeryClearPageElementsCommand()) + surgeryCmd.AddCommand(newSurgeryClearPageElementsCommand()) + surgeryCmd.AddCommand(newSurgeryFreelistCommand()) - return cmd + return surgeryCmd } func newSurgeryClearPageElementsCommand() *cobra.Command { @@ -42,7 +43,6 @@ func newSurgeryClearPageElementsCommand() *cobra.Command { } return nil }, - RunE: surgeryClearPageElementFunc, } @@ -78,3 +78,53 @@ func surgeryClearPageElementFunc(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stdout, "All elements in [%d, %d) in page %d were cleared\n", surgeryStartElementIdx, surgeryEndElementIdx, surgeryPageId) return nil } + +// TODO(ahrtr): add `bbolt surgery freelist rebuild/check ...` commands, +// and move all `surgery freelist` commands into a separate file, +// e.g command_surgery_freelist.go. +func newSurgeryFreelistCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "freelist ", + Short: "freelist related surgery commands", + } + + cmd.AddCommand(newSurgeryFreelistAbandonCommand()) + + return cmd +} + +func newSurgeryFreelistAbandonCommand() *cobra.Command { + abandonFreelistCmd := &cobra.Command{ + Use: "abandon [options]", + Short: "Abandon the freelist from both meta pages", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("db file path not provided") + } + if len(args) > 1 { + return errors.New("too many arguments") + } + return nil + }, + RunE: surgeryFreelistAbandonFunc, + } + + abandonFreelistCmd.Flags().StringVar(&surgeryTargetDBFilePath, "output", "", "path to the target db file") + + return abandonFreelistCmd +} + +func surgeryFreelistAbandonFunc(cmd *cobra.Command, args []string) error { + srcDBPath := args[0] + + if err := copyFile(srcDBPath, surgeryTargetDBFilePath); err != nil { + return fmt.Errorf("[abandon-freelist] copy file failed: %w", err) + } + + if err := surgeon.ClearFreelist(surgeryTargetDBFilePath); err != nil { + return fmt.Errorf("abandom-freelist command failed: %w", err) + } + + fmt.Fprintf(os.Stdout, "The freelist was abandoned in both meta pages.\nIt may cause some delay on next startup because bbolt needs to scan the whole db to reconstruct the free list.\n") + return nil +} diff --git a/cmd/bbolt/command_surgery_cobra_test.go b/cmd/bbolt/command_surgery_cobra_test.go index 3016a96..63103cd 100644 --- a/cmd/bbolt/command_surgery_cobra_test.go +++ b/cmd/bbolt/command_surgery_cobra_test.go @@ -11,6 +11,7 @@ import ( bolt "go.etcd.io/bbolt" main "go.etcd.io/bbolt/cmd/bbolt" "go.etcd.io/bbolt/internal/btesting" + "go.etcd.io/bbolt/internal/common" "go.etcd.io/bbolt/internal/guts_cli" ) @@ -428,3 +429,31 @@ func testSurgeryClearPageElementsWithOverflow(t *testing.T, startIdx, endIdx int compareDataAfterClearingElement(t, srcPath, output, pageId, false, startIdx, endIdx) } + +func TestSurgery_Freelist_Abandon(t *testing.T) { + pageSize := 4096 + db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: pageSize}) + srcPath := db.Path() + + defer requireDBNoChange(t, dbData(t, srcPath), srcPath) + + rootCmd := main.NewRootCommand() + output := filepath.Join(t.TempDir(), "db") + rootCmd.SetArgs([]string{ + "surgery", "freelist", "abandon", srcPath, + "--output", output, + }) + err := rootCmd.Execute() + require.NoError(t, err) + + meta0 := loadMetaPage(t, output, 0) + assert.Equal(t, common.PgidNoFreelist, meta0.Freelist()) + meta1 := loadMetaPage(t, output, 1) + assert.Equal(t, common.PgidNoFreelist, meta1.Freelist()) +} + +func loadMetaPage(t *testing.T, dbPath string, pageID uint64) *common.Meta { + _, buf, err := guts_cli.ReadPage(dbPath, 0) + require.NoError(t, err) + return common.LoadPageMeta(buf) +} diff --git a/internal/surgeon/surgeon.go b/internal/surgeon/surgeon.go index e69eb72..1f74e17 100644 --- a/internal/surgeon/surgeon.go +++ b/internal/surgeon/surgeon.go @@ -104,7 +104,7 @@ func ClearPageElements(path string, pgId common.Pgid, start, end int, abandonFre if preOverflow != p.Overflow() || p.IsBranchPage() { if abandonFreelist { - return false, clearFreelist(path) + return false, ClearFreelist(path) } return true, nil } @@ -112,7 +112,7 @@ func ClearPageElements(path string, pgId common.Pgid, start, end int, abandonFre return false, nil } -func clearFreelist(path string) error { +func ClearFreelist(path string) error { if err := clearFreelistInMetaPage(path, 0); err != nil { return fmt.Errorf("clearFreelist on meta page 0 failed: %w", err) }