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