1// SPDX-License-Identifier: ISC 2// Copyright (c) 2014-2020 Bitmark Inc. 3// Use of this source code is governed by an ISC 4// license that can be found in the LICENSE file. 5 6package main 7 8import ( 9 "bytes" 10 "encoding/hex" 11 "fmt" 12 "os" 13 "reflect" 14 "strconv" 15 "strings" 16 17 "github.com/bitmark-inc/bitmarkd/storage" 18 "github.com/bitmark-inc/exitwithstatus" 19 "github.com/bitmark-inc/getoptions" 20 "github.com/bitmark-inc/logger" 21) 22 23// set by the linker: go build -ldflags "-X main.version=M.N" ./... 24var version = "zero" // do not change this value 25 26// colours 27const ( 28 keyColour1 = "\033[1;36m" 29 keyColour2 = "\033[1;31m" 30 valColour1 = "\033[1;33m" 31 valColour2 = "\033[1;34m" 32 delColour1 = "\033[1;35m" 33 delColour2 = "\033[0;35m" 34 delColour3 = "\033[0;31m" 35 delColour4 = "\033[1;35m" 36 nodelColour = "\033[1;32m" 37 endColour = "\033[0m" 38) 39 40// main program 41func main() { 42 // ensure exit handler is first 43 defer exitwithstatus.Handler() 44 45 flags := []getoptions.Option{ 46 {Long: "help", HasArg: getoptions.NO_ARGUMENT, Short: 'h'}, 47 {Long: "verbose", HasArg: getoptions.NO_ARGUMENT, Short: 'v'}, 48 {Long: "version", HasArg: getoptions.NO_ARGUMENT, Short: 'V'}, 49 {Long: "list", HasArg: getoptions.NO_ARGUMENT, Short: 'l'}, 50 {Long: "delete", HasArg: getoptions.NO_ARGUMENT, Short: 'd'}, 51 {Long: "early", HasArg: getoptions.NO_ARGUMENT, Short: 'e'}, 52 {Long: "colour", HasArg: getoptions.NO_ARGUMENT, Short: 'g'}, 53 {Long: "ascii", HasArg: getoptions.NO_ARGUMENT, Short: 'a'}, 54 {Long: "file", HasArg: getoptions.REQUIRED_ARGUMENT, Short: 'f'}, 55 {Long: "count", HasArg: getoptions.REQUIRED_ARGUMENT, Short: 'c'}, 56 } 57 58 program, options, arguments, err := getoptions.GetOS(flags) 59 if nil != err { 60 exitwithstatus.Message("%s: getoptions error: %s", program, err) 61 } 62 63 if len(options["version"]) > 0 { 64 exitwithstatus.Message("%s: version: %s", program, version) 65 } 66 67 if len(options["list"]) > 0 { 68 69 // this will be a struct type 70 poolType := reflect.TypeOf(storage.Pool) 71 72 // print all available tags 73 fmt.Printf(" tags:\n") 74 for i := 0; i < poolType.NumField(); i += 1 { 75 fieldInfo := poolType.Field(i) 76 prefixTag := fieldInfo.Tag.Get("prefix") 77 fmt.Printf(" %s → %s\n", prefixTag, fieldInfo.Name) 78 } 79 return 80 } 81 82 if len(options["help"]) > 0 || 0 == len(arguments) || 1 != len(options["file"]) { 83 exitwithstatus.Message("usage: %s [--help] [--verbose] [--quiet] [--count=N] --file=FILE tag [--list] [key-prefix]", program) 84 } 85 86 // stop if prefix no longer matches 87 earlyStop := len(options["early"]) > 0 88 89 colour := len(options["colour"]) > 0 90 ascii := len(options["ascii"]) > 0 91 delete := len(options["delete"]) > 0 92 verbose := len(options["verbose"]) > 0 93 94 count := 10 95 if len(options["count"]) > 0 { 96 count, err = strconv.Atoi(options["count"][0]) 97 if nil != err { 98 exitwithstatus.Message("%s: convert count error: %s", program, err) 99 } 100 if count < 1 { 101 exitwithstatus.Message("%s: invalid count: %d", program, count) 102 } 103 } 104 105 filename := options["file"][0] 106 tag := arguments[0] 107 if verbose { 108 fmt.Printf("read tag: %s from file: %q\n", tag, filename) 109 } 110 111 prefix := []byte(nil) 112 if len(arguments) > 1 { 113 prefix, err = hex.DecodeString(arguments[1]) 114 if nil != err { 115 exitwithstatus.Message("%s: convert prefix error: %s", program, err) 116 } 117 } 118 119 logging := logger.Configuration{ 120 Directory: ".", 121 File: "bitmark-dumpdb.log", 122 Size: 1048576, 123 Count: 10, 124 Console: true, 125 Levels: map[string]string{ 126 logger.DefaultTag: "critical", 127 }, 128 } 129 130 // start logging 131 if err = logger.Initialise(logging); nil != err { 132 exitwithstatus.Message("%s: logger setup failed with error: %s", program, err) 133 } 134 defer logger.Finalise() 135 136 // start of main processing 137 err = storage.Initialise(filename, storage.ReadOnly) 138 if nil != err { 139 exitwithstatus.Message("%s: storage setup failed with error: %s", program, err) 140 } 141 142 defer storage.Finalise() 143 144 // this will be a struct type 145 poolType := reflect.TypeOf(storage.Pool) 146 147 // read-only access 148 poolValue := reflect.ValueOf(storage.Pool) 149 150 // the handle 151 //p := (*storage.PoolHandle)(nil) 152 // write access to p as a Value 153 //pvalue := reflect.ValueOf(&p).Elem() 154 155 // scan each field to locate tag 156 var p reflect.Value 157tag_scan: 158 for i := 0; i < poolType.NumField(); i += 1 { 159 fieldInfo := poolType.Field(i) 160 prefixTag := fieldInfo.Tag.Get("prefix") 161 if tag == prefixTag { 162 //pvalue.Set(poolValue.Field(i)) 163 p = poolValue.Field(i) 164 break tag_scan 165 } 166 167 } 168 if p.IsNil() { 169 exitwithstatus.Message("%s: no pool corresponding to: %q", program, tag) 170 } 171 172 // dump the items as hex 173 cf := p.MethodByName("NewFetchCursor") 174 if !cf.IsValid() { 175 exitwithstatus.Message("%s: no cursor access corresponding to: %q", program, tag) 176 } 177 178 //cursor := p.NewFetchCursor() 179 180 cursor := (*storage.FetchCursor)(nil) 181 // write access to p as a Value 182 cValue := reflect.ValueOf(&cursor).Elem() 183 cValue.Set(cf.Call(nil)[0]) 184 185 if len(prefix) > 0 { 186 cursor.Seek(prefix) 187 } 188 189 data, err := cursor.Fetch(count) 190 if nil != err { 191 exitwithstatus.Message("%s: error on Fetch: %s", program, err) 192 } 193 194 l := len(prefix) 195 196 ck1 := "" 197 ck2 := "" 198 cv1 := "" 199 cv2 := "" 200 cd1 := "" 201 cd2 := "" 202 cd3 := "" 203 cd4 := "" 204 cn := "" 205 ce := "" 206 if colour { 207 ck1 = keyColour1 208 ck2 = keyColour2 209 cv1 = valColour1 210 cv2 = valColour2 211 cd1 = delColour1 212 cd2 = delColour2 213 cd3 = delColour3 214 cd4 = delColour4 215 cn = nodelColour 216 ce = endColour 217 } 218print_loop: 219 for i, e := range data { 220 if earlyStop && len(e.Key) >= len(prefix) && !bytes.Equal(prefix, e.Key[:l]) { 221 fmt.Printf("*** early stop\n") 222 break print_loop 223 } 224 225 fmt.Printf("%d: %sKey: %s%x%s\n", i, ck1, ck2, e.Key, ce) 226 if ascii { 227 prefix := fmt.Sprintf("%d: %sVal: %s", i, cv1, cv2) 228 suffix := ce 229 hexDump(prefix, suffix, e.Value) 230 231 } else { 232 fmt.Printf("%d: %sVal: %s%x%s\n", i, cv1, cv2, e.Value, ce) 233 } 234 if delete { 235 delete_loop: 236 for { 237 fmt.Printf("%d: %sDelete Key: %s%x%s ? [yNq]: ", i, cd1, cd2, e.Key, ce) 238 239 buffer := make([]byte, 100) 240 n, err := os.Stdin.Read(buffer) 241 if nil != err { 242 exitwithstatus.Message("%s: error on Stdin.Read: %e", program, err) 243 } 244 245 response := strings.TrimSpace(string(buffer[:n])) 246 switch strings.ToLower(response) { 247 248 case "y", "yes": 249 //p.Delete(e.Key) 250 deleteRecord := p.MethodByName("Delete") 251 if !deleteRecord.IsValid() { 252 exitwithstatus.Message("%s: no Delete method corresponding to: %q", program, tag) 253 } 254 deleteRecord.Call([]reflect.Value{reflect.ValueOf(e.Key)}) 255 fmt.Printf("%d: %s***DELETED: %s%x%s\n", i, cd3, cd4, e.Key, ce) 256 break delete_loop 257 258 case "", "n", "no": 259 fmt.Printf("%d: %sRetain Key: %s%x%s\n", i, cn, ck2, e.Key, ce) 260 break delete_loop 261 262 case "q", "quit", "e", "exit", "x": 263 fmt.Printf("Terminated\n") 264 return 265 266 default: 267 fmt.Printf("Please answer yes or no\n") 268 } 269 } 270 } 271 } 272} 273 274// dump hex data on stdout 275func hexDump(prefix string, suffix string, data []byte) { 276 address := 0 277 const bytesPerLine = 32 278 for i := 0; i < len(data); i += bytesPerLine { 279 fmt.Printf("%s%04x ", prefix, address) 280 address += bytesPerLine 281 for j := 0; j < bytesPerLine; j += 1 { 282 if bytesPerLine/2 == j { 283 fmt.Printf(" ") 284 } 285 if i+j < len(data) { 286 fmt.Printf("%02x ", data[i+j]) 287 } else { 288 fmt.Printf(" ") 289 } 290 } 291 fmt.Printf(" |") 292 ascii_loop: 293 for j := 0; j < bytesPerLine; j += 1 { 294 if i+j < len(data) { 295 c := data[i+j] 296 if c < 32 || c >= 127 { 297 c = '.' 298 } 299 fmt.Printf("%c", c) 300 301 } else { 302 break ascii_loop 303 } 304 } 305 fmt.Printf("|%s\n", suffix) 306 } 307} 308