-
Notifications
You must be signed in to change notification settings - Fork 628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix CLI tool print entire freelist. Add option to dump leaf element keys/value bytes. #50
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
*.test | ||
*.swp | ||
/bin/ | ||
cmd/bolt/bolt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 "page-item": | ||
return newPageItemCommand(m).Run(args[1:]...) | ||
case "get": | ||
return newGetCommand(m).Run(args[1:]...) | ||
case "info": | ||
|
@@ -153,6 +155,7 @@ The commands are: | |
help print this screen | ||
page print one or more pages in human readable format | ||
pages print list of pages with their types | ||
page-item print the key and value of a page item. | ||
stats iterate over all pages and generate usage stats | ||
|
||
Use "bolt [command] -h" for more information about a command. | ||
|
@@ -416,9 +419,173 @@ 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") | ||
} | ||
|
||
// PageItemCommand represents the "page-item" command execution. | ||
type PageItemCommand struct { | ||
Stdin io.Reader | ||
Stdout io.Writer | ||
Stderr io.Writer | ||
} | ||
|
||
// newPageItemCommand returns a PageItemCommand. | ||
func newPageItemCommand(m *Main) *PageItemCommand { | ||
return &PageItemCommand{ | ||
Stdin: m.Stdin, | ||
Stdout: m.Stdout, | ||
Stderr: m.Stderr, | ||
} | ||
} | ||
|
||
type pageItemOptions struct { | ||
help bool | ||
keyOnly bool | ||
valueOnly bool | ||
format string | ||
} | ||
|
||
// Run executes the command. | ||
func (cmd *PageItemCommand) Run(args ...string) error { | ||
// Parse flags. | ||
options := &pageItemOptions{} | ||
fs := flag.NewFlagSet("", flag.ContinueOnError) | ||
fs.BoolVar(&options.keyOnly, "key-only", false, "Print only the key") | ||
fs.BoolVar(&options.valueOnly, "value-only", false, "Print only the value") | ||
fs.StringVar(&options.format, "format", "ascii-encoded", "Output format. One of: ascii-encoded|hex|bytes") | ||
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), options.format) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
if !options.keyOnly { | ||
err := cmd.PrintLeafItemValue(cmd.Stdout, buf, uint16(itemID), options.format) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// leafPageElement retrieves a leaf page element. | ||
func (cmd *PageItemCommand) 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 | ||
} | ||
|
||
// writeBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes. | ||
func (cmd *PageItemCommand) writeBytes(w io.Writer, b []byte, format string) error { | ||
switch format { | ||
case "ascii-encoded": | ||
_, err := fmt.Fprintf(w, "%q", b) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = fmt.Fprintf(w, "\n") | ||
return err | ||
case "hex": | ||
_, err := fmt.Fprintf(w, "%x", b) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = fmt.Fprintf(w, "\n") | ||
return err | ||
case "bytes": | ||
_, err := w.Write(b) | ||
return err | ||
default: | ||
return fmt.Errorf("writeBytes: unsupported format: %s", format) | ||
} | ||
} | ||
|
||
// PrintLeafItemKey writes the bytes of a leaf element's key. | ||
func (cmd *PageItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, index uint16, format string) error { | ||
e, err := cmd.leafPageElement(pageBytes, index) | ||
if err != nil { | ||
return err | ||
} | ||
return cmd.writeBytes(w, e.key(), format) | ||
} | ||
|
||
// PrintLeafItemKey writes the bytes of a leaf element's value. | ||
func (cmd *PageItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, index uint16, format string) error { | ||
e, err := cmd.leafPageElement(pageBytes, index) | ||
if err != nil { | ||
return err | ||
} | ||
return cmd.writeBytes(w, e.value(), format) | ||
} | ||
|
||
// Usage returns the help message. | ||
func (cmd *PageItemCommand) Usage() string { | ||
return strings.TrimLeft(` | ||
usage: bolt page-item [options] PATH pageid itemid | ||
|
||
Additional options include: | ||
|
||
--key-only | ||
Print only the key | ||
--value-only | ||
Print only the value | ||
--format | ||
Output format. One of: ascii-encoded|hex|bytes (default=ascii-encoded) | ||
|
||
page-item prints a page item key and value. | ||
`, "\n") | ||
} | ||
|
||
|
@@ -592,13 +759,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 +829,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...] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure enough. Fixed. |
||
|
||
Page prints one or more pages in human readable format. | ||
`, "\n") | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the output with no flags exactly matches the physical layout since this is a raw dump.
etcdctl's default output mode inserts a newline and in retrospect I think it makes the output really difficult / impossible to parse, but it's too late now.
printf("%q\n%q\n",key,val)
seems like the best/easiest human-readable way to print binary data, if that's worth pursuing in the future.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we do something like this:
?
This solves a couple problems: