Skip to content

Commit

Permalink
Fix bolt CLI tool print entire freelist, and to dump keys/value bytes…
Browse files Browse the repository at this point in the history
… of leaf elements.
  • Loading branch information
jpbetz committed Sep 15, 2017
1 parent 2760028 commit e4081c7
Showing 1 changed file with 145 additions and 5 deletions.
150 changes: 145 additions & 5 deletions cmd/bolt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func (m *Main) Run(args ...string) error {
return newCompactCommand(m).Run(args[1:]...)
case "dump":
return newDumpCommand(m).Run(args[1:]...)
case "dump-item":
return newDumpItemCommand(m).Run(args[1:]...)
case "get":
return newGetCommand(m).Run(args[1:]...)
case "info":
Expand Down Expand Up @@ -147,6 +149,7 @@ The commands are:
check verifies integrity of bolt database
compact copies a bolt database, compacting it in the process
dump print a hexadecimal dump of a single page
dump-item print the bytes of a page item key or value.
get print the value of a key in a bucket
info print basic info
keys print a list of keys in a bucket
Expand Down Expand Up @@ -416,9 +419,137 @@ func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSi
// Usage returns the help message.
func (cmd *DumpCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt dump -page PAGEID PATH
usage: bolt dump PATH pageid [pageid...]
Dump prints a hexadecimal dump of a single page.
Dump prints a hexadecimal dump of one or more pages.
`, "\n")
}

// DumpItemCommand represents the "dump-item" command execution.
type DumpItemCommand struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}

// newDumpItemCommand returns a DumpItemCommand.
func newDumpItemCommand(m *Main) *DumpItemCommand {
return &DumpItemCommand{
Stdin: m.Stdin,
Stdout: m.Stdout,
Stderr: m.Stderr,
}
}

type dumpItemOptions struct {
help bool
key bool
value bool
}

// Run executes the command.
func (cmd *DumpItemCommand) Run(args ...string) error {
// Parse flags.
options := &dumpItemOptions{}
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.BoolVar(&options.key, "key", false, "Print out the raw bytes of the leaf item key.")
fs.BoolVar(&options.value, "value", false, "Print out the raw bytes of the leaf item value.")
fs.BoolVar(&options.help, "h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if options.help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}

if options.key && options.value {
return fmt.Errorf("The --key or --value flag may be set, but not both.")
}

// Require database path and page id.
path := fs.Arg(0)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
}

// Read page id.
pageID, err := strconv.Atoi(fs.Arg(1))
if err != nil {
return err
}

// Read item id.
itemID, err := strconv.Atoi(fs.Arg(2))
if err != nil {
return err
}

// Open database file handler.
f, err := os.Open(path)
if err != nil {
return err
}
defer func() { _ = f.Close() }()

// Retrieve page info and page size.
p, buf, err := ReadPage(path, pageID)
if err != nil {
return err
}

if p.Type() != "leaf" {
return fmt.Errorf("")
}

switch {
case options.key:
return cmd.PrintLeafItemKey(cmd.Stdout, buf, uint16(itemID))
case options.value:
return cmd.PrintLeafItemValue(cmd.Stdout, buf, uint16(itemID))
default:
return fmt.Errorf("Either the --key or --value flag must be set.")
}
}

// leafPageElement retrieves a leaf page element.
func (cmd *DumpItemCommand) leafPageElement(pageBytes []byte, index uint16) (*leafPageElement, error) {
p := (*page)(unsafe.Pointer(&pageBytes[0]))

if index >= p.count {
return nil, fmt.Errorf("leafPageElement: expected item index less than %d, but got %d.", p.count, index)
}

return p.leafPageElement(index), nil
}

// PrintLeafItemKey writes the bytes of a leaf element's key.
func (cmd *DumpItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, index uint16) error {
e, err := cmd.leafPageElement(pageBytes, index)
if err != nil {
return err
}
_, err = w.Write(e.key())
return err
}

// PrintLeafItemKey writes the bytes of a leaf element's value.
func (cmd *DumpItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, index uint16) error {
e, err := cmd.leafPageElement(pageBytes, index)
if err != nil {
return err
}
_, err = w.Write(e.value())
return err
}

// Usage returns the help message.
func (cmd *DumpItemCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt dump-item [--key|--value] PATH pageid itemid
Dump-item prints the bytes of a page item key or value.
`, "\n")
}

Expand Down Expand Up @@ -592,13 +723,22 @@ func (cmd *PageCommand) PrintBranch(w io.Writer, buf []byte) error {
func (cmd *PageCommand) PrintFreelist(w io.Writer, buf []byte) error {
p := (*page)(unsafe.Pointer(&buf[0]))

// Check for overflow and, if present, adjust starting index and actual element count.
idx, count := 0, int(p.count)
if p.count == 0xFFFF {
idx = 1
count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
}

// Print number of items.
fmt.Fprintf(w, "Item Count: %d\n", p.count)
fmt.Fprintf(w, "Item Count: %d\n", count)
fmt.Fprintf(w, "Overflow: %d\n", p.overflow)

fmt.Fprintf(w, "\n")

// Print each page in the freelist.
ids := (*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr))
for i := uint16(0); i < p.count; i++ {
for i := int(idx); i < count; i++ {
fmt.Fprintf(w, "%d\n", ids[i])
}
fmt.Fprintf(w, "\n")
Expand Down Expand Up @@ -653,7 +793,7 @@ func (cmd *PageCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSi
// Usage returns the help message.
func (cmd *PageCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt page -page PATH pageid [pageid...]
usage: bolt page PATH pageid [pageid...]
Page prints one or more pages in human readable format.
`, "\n")
Expand Down

0 comments on commit e4081c7

Please sign in to comment.