aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorBen Johnson <benbjohnson@yahoo.com>2015-04-17 13:28:32 -0600
committerBen Johnson <benbjohnson@yahoo.com>2015-04-17 13:28:32 -0600
commit73a3aa1ccc3b318b699591f5893d9f410945a5e9 (patch)
tree241fc8aaf05ed863d0762551641cfcbef9b6f061 /cmd
parentAdd 'bolt dump' command. (diff)
downloaddedo-73a3aa1ccc3b318b699591f5893d9f410945a5e9.tar.gz
dedo-73a3aa1ccc3b318b699591f5893d9f410945a5e9.tar.xz
Add 'bolt page' command.
Diffstat (limited to 'cmd')
-rw-r--r--cmd/bolt/main.go473
1 files changed, 466 insertions, 7 deletions
diff --git a/cmd/bolt/main.go b/cmd/bolt/main.go
index 246d04c..2c4c414 100644
--- a/cmd/bolt/main.go
+++ b/cmd/bolt/main.go
@@ -15,6 +15,9 @@ import (
"strconv"
"strings"
"time"
+ "unicode"
+ "unicode/utf8"
+ "unsafe"
"github.com/boltdb/bolt"
)
@@ -45,8 +48,17 @@ var (
// ErrPageIDRequired is returned when a required page id is not specified.
ErrPageIDRequired = errors.New("page id required")
+
+ // ErrPageNotFound is returned when specifying a page above the high water mark.
+ ErrPageNotFound = errors.New("page not found")
+
+ // ErrPageFreed is returned when reading a page that has already been freed.
+ ErrPageFreed = errors.New("page freed")
)
+// PageHeaderSize represents the size of the bolt.page header.
+const PageHeaderSize = 16
+
func main() {
m := NewMain()
if err := m.Run(os.Args[1:]...); err == ErrUsage {
@@ -94,6 +106,8 @@ func (m *Main) Run(args ...string) error {
return newDumpCommand(m).Run(args[1:]...)
case "info":
return newInfoCommand(m).Run(args[1:]...)
+ case "page":
+ return newPageCommand(m).Run(args[1:]...)
case "pages":
return newPagesCommand(m).Run(args[1:]...)
case "stats":
@@ -290,7 +304,6 @@ func (cmd *DumpCommand) Run(args ...string) error {
// Parse flags.
fs := flag.NewFlagSet("", flag.ContinueOnError)
help := fs.Bool("h", false, "")
- pageID := fs.Int("page", -1, "")
if err := fs.Parse(args); err != nil {
return err
} else if *help {
@@ -304,17 +317,21 @@ func (cmd *DumpCommand) Run(args ...string) error {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
- } else if *pageID == -1 {
+ }
+
+ // Read page ids.
+ pageIDs, err := atois(fs.Args()[1:])
+ if err != nil {
+ return err
+ } else if len(pageIDs) == 0 {
return ErrPageIDRequired
}
// Open database to retrieve page size.
- db, err := bolt.Open(path, 0666, nil)
+ pageSize, err := ReadPageSize(path)
if err != nil {
return err
}
- pageSize := db.Info().PageSize
- _ = db.Close()
// Open database file handler.
f, err := os.Open(path)
@@ -323,8 +340,20 @@ func (cmd *DumpCommand) Run(args ...string) error {
}
defer func() { _ = f.Close() }()
- // Print page to stdout.
- return cmd.PrintPage(cmd.Stdout, f, *pageID, pageSize)
+ // Print each page listed.
+ for i, pageID := range pageIDs {
+ // Print a separator.
+ if i > 0 {
+ fmt.Fprintln(cmd.Stdout, "===============================================\n")
+ }
+
+ // Print page to stdout.
+ if err := cmd.PrintPage(cmd.Stdout, f, pageID, pageSize); err != nil {
+ return err
+ }
+ }
+
+ return nil
}
// PrintPage prints a given page as hexidecimal.
@@ -381,6 +410,243 @@ Dump prints a hexidecimal dump of a single page.
`, "\n")
}
+// PageCommand represents the "page" command execution.
+type PageCommand struct {
+ Stdin io.Reader
+ Stdout io.Writer
+ Stderr io.Writer
+}
+
+// newPageCommand returns a PageCommand.
+func newPageCommand(m *Main) *PageCommand {
+ return &PageCommand{
+ Stdin: m.Stdin,
+ Stdout: m.Stdout,
+ Stderr: m.Stderr,
+ }
+}
+
+// Run executes the command.
+func (cmd *PageCommand) Run(args ...string) error {
+ // Parse flags.
+ fs := flag.NewFlagSet("", flag.ContinueOnError)
+ help := fs.Bool("h", false, "")
+ if err := fs.Parse(args); err != nil {
+ return err
+ } else if *help {
+ fmt.Fprintln(cmd.Stderr, cmd.Usage())
+ return ErrUsage
+ }
+
+ // 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 ids.
+ pageIDs, err := atois(fs.Args()[1:])
+ if err != nil {
+ return err
+ } else if len(pageIDs) == 0 {
+ return ErrPageIDRequired
+ }
+
+ // Open database file handler.
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer func() { _ = f.Close() }()
+
+ // Print each page listed.
+ for i, pageID := range pageIDs {
+ // Print a separator.
+ if i > 0 {
+ fmt.Fprintln(cmd.Stdout, "===============================================\n")
+ }
+
+ // Retrieve page info and page size.
+ p, buf, err := ReadPage(path, pageID)
+ if err != nil {
+ return err
+ }
+
+ // Print basic page info.
+ fmt.Fprintf(cmd.Stdout, "Page ID: %d\n", p.id)
+ fmt.Fprintf(cmd.Stdout, "Page Type: %s\n", p.Type())
+ fmt.Fprintf(cmd.Stdout, "Total Size: %d bytes\n", len(buf))
+
+ // Print type-specific data.
+ switch p.Type() {
+ case "meta":
+ err = cmd.PrintMeta(cmd.Stdout, buf)
+ case "leaf":
+ err = cmd.PrintLeaf(cmd.Stdout, buf)
+ case "branch":
+ err = cmd.PrintBranch(cmd.Stdout, buf)
+ case "freelist":
+ err = cmd.PrintFreelist(cmd.Stdout, buf)
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// PrintMeta prints the data from the meta page.
+func (cmd *PageCommand) PrintMeta(w io.Writer, buf []byte) error {
+ m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize]))
+ fmt.Fprintf(w, "Version: %d\n", m.version)
+ fmt.Fprintf(w, "Page Size: %d bytes\n", m.pageSize)
+ fmt.Fprintf(w, "Flags: %08x\n", m.flags)
+ fmt.Fprintf(w, "Root: <pgid=%d>\n", m.root.root)
+ fmt.Fprintf(w, "Freelist: <pgid=%d>\n", m.freelist)
+ fmt.Fprintf(w, "HWM: <pgid=%d>\n", m.pgid)
+ fmt.Fprintf(w, "Txn ID: %d\n", m.txid)
+ fmt.Fprintf(w, "Checksum: %016x\n", m.checksum)
+ fmt.Fprintf(w, "\n")
+ return nil
+}
+
+// PrintLeaf prints the data for a leaf page.
+func (cmd *PageCommand) PrintLeaf(w io.Writer, buf []byte) error {
+ p := (*page)(unsafe.Pointer(&buf[0]))
+
+ // Print number of items.
+ fmt.Fprintf(w, "Item Count: %d\n", p.count)
+ fmt.Fprintf(w, "\n")
+
+ // Print each key/value.
+ for i := uint16(0); i < p.count; i++ {
+ e := p.leafPageElement(i)
+
+ // Format key as string.
+ var k string
+ if isPrintable(string(e.key())) {
+ k = fmt.Sprintf("%q", string(e.key()))
+ } else {
+ k = fmt.Sprintf("%x", string(e.key()))
+ }
+
+ // Format value as string.
+ var v string
+ if (e.flags & uint32(bucketLeafFlag)) != 0 {
+ b := (*bucket)(unsafe.Pointer(&e.value()[0]))
+ v = fmt.Sprintf("<pgid=%d,seq=%d>", b.root, b.sequence)
+ } else if isPrintable(string(e.value())) {
+ k = fmt.Sprintf("%q", string(e.value()))
+ } else {
+ k = fmt.Sprintf("%x", string(e.value()))
+ }
+
+ fmt.Fprintf(w, "%s: %s\n", k, v)
+ }
+ fmt.Fprintf(w, "\n")
+ return nil
+}
+
+// PrintBranch prints the data for a leaf page.
+func (cmd *PageCommand) PrintBranch(w io.Writer, buf []byte) error {
+ p := (*page)(unsafe.Pointer(&buf[0]))
+
+ // Print number of items.
+ fmt.Fprintf(w, "Item Count: %d\n", p.count)
+ fmt.Fprintf(w, "\n")
+
+ // Print each key/value.
+ for i := uint16(0); i < p.count; i++ {
+ e := p.branchPageElement(i)
+
+ // Format key as string.
+ var k string
+ if isPrintable(string(e.key())) {
+ k = fmt.Sprintf("%q", string(e.key()))
+ } else {
+ k = fmt.Sprintf("%x", string(e.key()))
+ }
+
+ fmt.Fprintf(w, "%s: <pgid=%d>\n", k, e.pgid)
+ }
+ fmt.Fprintf(w, "\n")
+ return nil
+}
+
+// PrintFreelist prints the data for a freelist page.
+func (cmd *PageCommand) PrintFreelist(w io.Writer, buf []byte) error {
+ p := (*page)(unsafe.Pointer(&buf[0]))
+
+ // Print number of items.
+ fmt.Fprintf(w, "Item Count: %d\n", p.count)
+ 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++ {
+ fmt.Fprintf(w, "%d\n", ids[i])
+ }
+ fmt.Fprintf(w, "\n")
+ return nil
+}
+
+// PrintPage prints a given page as hexidecimal.
+func (cmd *PageCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSize int) error {
+ const bytesPerLineN = 16
+
+ // Read page into buffer.
+ buf := make([]byte, pageSize)
+ addr := pageID * pageSize
+ if n, err := r.ReadAt(buf, int64(addr)); err != nil {
+ return err
+ } else if n != pageSize {
+ return io.ErrUnexpectedEOF
+ }
+
+ // Write out to writer in 16-byte lines.
+ var prev []byte
+ var skipped bool
+ for offset := 0; offset < pageSize; offset += bytesPerLineN {
+ // Retrieve current 16-byte line.
+ line := buf[offset : offset+bytesPerLineN]
+ isLastLine := (offset == (pageSize - bytesPerLineN))
+
+ // If it's the same as the previous line then print a skip.
+ if bytes.Equal(line, prev) && !isLastLine {
+ if !skipped {
+ fmt.Fprintf(w, "%07x *\n", addr+offset)
+ skipped = true
+ }
+ } else {
+ // Print line as hexadecimal in 2-byte groups.
+ fmt.Fprintf(w, "%07x %04x %04x %04x %04x %04x %04x %04x %04x\n", addr+offset,
+ line[0:2], line[2:4], line[4:6], line[6:8],
+ line[8:10], line[10:12], line[12:14], line[14:16],
+ )
+
+ skipped = false
+ }
+
+ // Save the previous line.
+ prev = line
+ }
+ fmt.Fprint(w, "\n")
+
+ return nil
+}
+
+// Usage returns the help message.
+func (cmd *PageCommand) Usage() string {
+ return strings.TrimLeft(`
+usage: bolt page -page PATH pageid [pageid...]
+
+Page prints one or more pages in human readable format.
+`, "\n")
+}
+
// PagesCommand represents the "pages" command execution.
type PagesCommand struct {
Stdin io.Reader
@@ -1069,3 +1335,196 @@ type PageError struct {
func (e *PageError) Error() string {
return fmt.Sprintf("page error: id=%d, err=%s", e.ID, e.Err)
}
+
+// isPrintable returns true if the string is valid unicode and contains only printable runes.
+func isPrintable(s string) bool {
+ if !utf8.ValidString(s) {
+ return false
+ }
+ for _, ch := range s {
+ if !unicode.IsPrint(ch) {
+ return false
+ }
+ }
+ return true
+}
+
+// ReadPage reads page info & full page data from a path.
+// This is not transactionally safe.
+func ReadPage(path string, pageID int) (*page, []byte, error) {
+ // Find page size.
+ pageSize, err := ReadPageSize(path)
+ if err != nil {
+ return nil, nil, fmt.Errorf("read page size: %s", err)
+ }
+
+ // Open database file.
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer f.Close()
+
+ // Read one block into buffer.
+ buf := make([]byte, pageSize)
+ if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
+ return nil, nil, err
+ } else if n != len(buf) {
+ return nil, nil, io.ErrUnexpectedEOF
+ }
+
+ // Determine total number of blocks.
+ p := (*page)(unsafe.Pointer(&buf[0]))
+ overflowN := p.overflow
+
+ // Re-read entire page (with overflow) into buffer.
+ buf = make([]byte, (int(overflowN)+1)*pageSize)
+ if n, err := f.ReadAt(buf, int64(pageID*pageSize)); err != nil {
+ return nil, nil, err
+ } else if n != len(buf) {
+ return nil, nil, io.ErrUnexpectedEOF
+ }
+ p = (*page)(unsafe.Pointer(&buf[0]))
+
+ return p, buf, nil
+}
+
+// ReadPageSize reads page size a path.
+// This is not transactionally safe.
+func ReadPageSize(path string) (int, error) {
+ // Open database file.
+ f, err := os.Open(path)
+ if err != nil {
+ return 0, err
+ }
+ defer f.Close()
+
+ // Read 4KB chunk.
+ buf := make([]byte, 4096)
+ if _, err := io.ReadFull(f, buf); err != nil {
+ return 0, err
+ }
+
+ // Read page size from metadata.
+ m := (*meta)(unsafe.Pointer(&buf[PageHeaderSize]))
+ return int(m.pageSize), nil
+}
+
+// atois parses a slice of strings into integers.
+func atois(strs []string) ([]int, error) {
+ var a []int
+ for _, str := range strs {
+ i, err := strconv.Atoi(str)
+ if err != nil {
+ return nil, err
+ }
+ a = append(a, i)
+ }
+ return a, nil
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+const maxAllocSize = 0xFFFFFFF
+
+// DO NOT EDIT. Copied from the "bolt" package.
+const (
+ branchPageFlag = 0x01
+ leafPageFlag = 0x02
+ metaPageFlag = 0x04
+ freelistPageFlag = 0x10
+)
+
+// DO NOT EDIT. Copied from the "bolt" package.
+const bucketLeafFlag = 0x01
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type pgid uint64
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type txid uint64
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type meta struct {
+ magic uint32
+ version uint32
+ pageSize uint32
+ flags uint32
+ root bucket
+ freelist pgid
+ pgid pgid
+ txid txid
+ checksum uint64
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type bucket struct {
+ root pgid
+ sequence uint64
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type page struct {
+ id pgid
+ flags uint16
+ count uint16
+ overflow uint32
+ ptr uintptr
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (p *page) Type() string {
+ if (p.flags & branchPageFlag) != 0 {
+ return "branch"
+ } else if (p.flags & leafPageFlag) != 0 {
+ return "leaf"
+ } else if (p.flags & metaPageFlag) != 0 {
+ return "meta"
+ } else if (p.flags & freelistPageFlag) != 0 {
+ return "freelist"
+ }
+ return fmt.Sprintf("unknown<%02x>", p.flags)
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (p *page) leafPageElement(index uint16) *leafPageElement {
+ n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index]
+ return n
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (p *page) branchPageElement(index uint16) *branchPageElement {
+ return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index]
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type branchPageElement struct {
+ pos uint32
+ ksize uint32
+ pgid pgid
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (n *branchPageElement) key() []byte {
+ buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
+ return buf[n.pos : n.pos+n.ksize]
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+type leafPageElement struct {
+ flags uint32
+ pos uint32
+ ksize uint32
+ vsize uint32
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (n *leafPageElement) key() []byte {
+ buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
+ return buf[n.pos : n.pos+n.ksize]
+}
+
+// DO NOT EDIT. Copied from the "bolt" package.
+func (n *leafPageElement) value() []byte {
+ buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
+ return buf[n.pos+n.ksize : n.pos+n.ksize+n.vsize]
+}