diff --git a/cmd/bolt/main.go b/cmd/bolt/main.go index aca43981d..323fa3bd0 100644 --- a/cmd/bolt/main.go +++ b/cmd/bolt/main.go @@ -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": @@ -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/value. get print the value of a key in a bucket info print basic info keys print a list of keys in a bucket @@ -416,9 +419,155 @@ 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 + keyOnly bool + valueOnly bool +} + +// Run executes the command. +func (cmd *DumpItemCommand) Run(args ...string) error { + // Parse flags. + options := &dumpItemOptions{} + fs := flag.NewFlagSet("", flag.ContinueOnError) + fs.BoolVar(&options.keyOnly, "key-only", false, "Only print the key.") + fs.BoolVar(&options.valueOnly, "value-only", false, "Only print the 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.keyOnly && options.valueOnly { + return fmt.Errorf("The --key-only or --value-only 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. + _, buf, err := ReadPage(path, pageID) + if err != nil { + return err + } + + if !options.valueOnly { + err = cmd.PrintLeafItemKey(cmd.Stdout, buf, uint16(itemID)) + if err != nil { + return err + } + } + if !options.keyOnly && !options.valueOnly { + _, err = cmd.Stdout.Write([]byte("\n")) + if err != nil { + return err + } + } + if !options.keyOnly { + err := cmd.PrintLeafItemValue(cmd.Stdout, buf, uint16(itemID)) + if err != nil { + return err + } + } + return nil +} + +// 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) + } + + if p.Type() != "leaf" { + return nil, fmt.Errorf("leafPageElement: expected page type of 'leaf', but got '%s'", p.Type()) + } + + 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 [options] PATH pageid itemid + +Additional options include: + + --key-only + Only print the key. + --value-only + Only print the value. + +Dump-item prints the bytes of a page item key/value. `, "\n") } @@ -592,13 +741,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") @@ -653,7 +811,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")