2 * Copyright (C) 2017 Cisco Inc.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2,
6 * as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // @author Changxue Deng <chadeng@cisco.com>
19 // A mabain command-line client
28 #include <readline/readline.h>
29 #include <readline/history.h>
38 #include "expr_parser.h"
40 using namespace mabain;
52 COMMAND_DELETE_ALL = 9,
54 COMMAND_RESET_N_WRITER = 11,
55 COMMAND_RESET_N_READER = 12,
56 COMMAND_FIND_LPREFIX = 13,
57 COMMAND_PRINT_HEADER = 14,
58 COMMAND_FIND_HEX = 15,
59 COMMAND_FIND_LPREFIX_HEX = 16,
60 COMMAND_RECLAIM_RESOURCES = 17,
61 COMMAND_PARSING_ERROR = 18,
64 volatile bool quit_mbc = false;
65 static void HandleSignal(int sig)
70 std::cerr << "process segfault\n";
84 static void usage(const char *prog)
86 std::cout << "Usage: " << prog << " -d mabain-directory [-im index-memcap] [-dm data-memcap] [-w] [-e query] [-s script-file]\n";
87 std::cout <<"\t-d mabain databse directory\n";
88 std::cout <<"\t-im index memcap\n";
89 std::cout <<"\t-dm data memcap\n";
90 std::cout <<"\t-w running in writer mode\n";
91 std::cout <<"\t-e run query on command line\n";
92 std::cout <<"\t-s run queries in a file\n";
96 static void show_help()
98 std::cout << "\tfind(\"key\")\t\tsearch entry by key\n";
99 std::cout << "\tfindPrefix(\"key\")\tsearch entry by key using longest prefix match\n";
100 std::cout << "\tfindAll\t\t\tlist all entries\n";
101 std::cout << "\tinsert(\"key\":\"value\")\tinsert a key-value pair\n";
102 std::cout << "\treplace(\"key\":\"value\")\treplace a key-value pair\n";
103 std::cout << "\tdelete(\"key\")\t\tdelete entry by key\n";
104 std::cout << "\tdeleteAll\t\tdelete all entries\n";
105 std::cout << "\tshow\t\t\tshow database statistics\n";
106 std::cout << "\thelp\t\t\tshow helps\n";
107 std::cout << "\tquit\t\t\tquit mabain client\n";
108 std::cout << "\tdecWriterCount\t\tClear writer count in shared memory header\n";
109 std::cout << "\tdecReaderCount\t\tdecrement reader count in shared memory header\n";
110 std::cout << "\tprintHeader\t\tPrint shared memory header\n";
111 std::cout << "\treclaimResources\tReclaim deleted resources\n";
114 static void trim_spaces(const char *cmd, std::string &cmd_trim)
122 if(*p == '\'' || *p == '\"')
124 cmd_trim.append(1, '\'');
127 else if(!isspace(*p) || quotation)
129 cmd_trim.append(1, *p);
136 static bool check_hex_output(std::string &cmd)
138 size_t pos = cmd.rfind(".hex()");
139 if(pos == std::string::npos)
141 if(pos == cmd.length()-6)
150 static int parse_key_value_pair(const std::string &kv_str,
154 // search for ':' that separates key and value pair
155 // currently this utility does not support quotation in
156 // quotation, e.g, "abc\"def" as key or value.
158 int quotation_cnt = 0;
159 for(size_t i = 0; i < kv_str.length(); i++)
161 if(kv_str[i] == '\'')
165 else if(kv_str[i] == ':')
167 // do not count the ':' in the key or value string.
168 if(quotation_cnt % 2 == 0)
179 ExprParser expr_key(kv_str.substr(0, pos));
180 if(expr_key.Evaluate(key) < 0)
183 ExprParser expr_value(kv_str.substr(pos+1));
184 if(expr_value.Evaluate(value) < 0)
190 static int parse_command(std::string &cmd,
195 bool hex_output = false;
202 if(cmd.compare("quit") == 0)
206 if(cmd.compare("show") == 0)
207 return COMMAND_STATS;
210 hex_output = check_hex_output(cmd);
211 if(cmd.compare(0, 5, "find(") == 0)
213 if(cmd[cmd.length()-1] != ')')
214 return COMMAND_UNKNOWN;
215 ExprParser expr(cmd.substr(5, cmd.length()-6));
216 if(expr.Evaluate(key) < 0)
217 return COMMAND_PARSING_ERROR;
219 return COMMAND_FIND_HEX;
222 else if(cmd.compare(0, 11, "findPrefix(") == 0)
224 if(cmd[cmd.length()-1] != ')')
225 return COMMAND_UNKNOWN;
226 ExprParser expr(cmd.substr(11, cmd.length()-12));
227 if(expr.Evaluate(key) < 0)
228 return COMMAND_PARSING_ERROR;
230 return COMMAND_FIND_LPREFIX_HEX;
231 return COMMAND_FIND_LPREFIX;
233 else if(cmd.compare("findAll") == 0)
234 return COMMAND_FIND_ALL;
237 if(cmd.compare(0, 7, "delete(") == 0)
239 if(cmd[cmd.length()-1] != ')')
240 return COMMAND_UNKNOWN;
241 ExprParser expr(cmd.substr(7, cmd.length()-8));
242 if(expr.Evaluate(key) < 0)
243 return COMMAND_PARSING_ERROR;
244 return COMMAND_DELETE;
246 else if(cmd.compare("deleteAll") == 0)
248 std::cout << "Do you want to delete all entries? Press \'Y\' to continue: ";
250 std::getline(std::cin, del_all);
251 if(del_all.length() == 0 || del_all[0] != 'Y')
253 return COMMAND_DELETE_ALL;
255 else if(cmd.compare("decReaderCount") == 0)
257 std::cout << "Do you want to decrement number of reader? Press \'y\' to continue: ";
258 std::getline(std::cin, yes);
259 if(yes.length() > 0 && yes[0] == 'y')
260 return COMMAND_RESET_N_READER;
263 else if(cmd.compare("decWriterCount") == 0)
265 std::cout << "Do you want to decrement number of writer? Press \'y\' to continue: ";
266 std::getline(std::cin, yes);
267 if(yes.length() > 0 && yes[0] == 'y')
268 return COMMAND_RESET_N_WRITER;
273 if(cmd.compare(0, 7, "insert(") == 0)
275 if(cmd[cmd.length()-1] != ')')
276 return COMMAND_UNKNOWN;
277 if(parse_key_value_pair(cmd.substr(7, cmd.length()-8), key, value) < 0)
278 return COMMAND_PARSING_ERROR;
279 return COMMAND_INSERT;
283 if(cmd.compare(0, 8, "replace(") == 0)
285 if(cmd[cmd.length()-1] != ')')
286 return COMMAND_UNKNOWN;
287 if(parse_key_value_pair(cmd.substr(8, cmd.length()-9), key, value) < 0)
288 return COMMAND_PARSING_ERROR;
289 return COMMAND_REPLACE;
291 else if(cmd.compare("reclaimResources") == 0)
292 return COMMAND_RECLAIM_RESOURCES;
294 return COMMAND_UNKNOWN;
297 if(cmd.compare("help") == 0)
301 if(cmd.compare("printHeader") == 0)
302 return COMMAND_PRINT_HEADER;
308 return COMMAND_UNKNOWN;
311 #define ENTRY_PER_PAGE 20
312 static void display_all_kvs(DB *db)
318 for(DB::iterator iter = db->begin(); iter != db->end(); ++iter)
321 std::cout << iter.key << ": " <<
322 std::string((char *)iter.value.buff, iter.value.data_len) << "\n";
323 if(count % ENTRY_PER_PAGE == 0)
325 std::string show_more;
326 std::cout << "Press \'y\' for displaying more: ";
327 std::getline(std::cin, show_more);
328 if(show_more.length() == 0 || show_more[0] != 'y')
334 static void display_output(const MBData &mbd, bool hex_output, bool prefix)
337 std::cout << "key length matched: " << mbd.match_len << "\n";
341 int len = mbd.data_len;
344 std::cout << "display the first 127 bytes\n";
347 if(bin_2_hex((const uint8_t *)mbd.buff, len, hex_buff, 256) < 0)
348 std::cout << "failed to convert binary to hex\n";
350 std::cout << hex_buff << "\n";
354 std::cout << std::string((char *)mbd.buff, mbd.data_len) << "\n";
358 static int RunCommand(int mode, DB *db, int cmd_id, const std::string &key, const std::string &value)
360 int rval = MBError::SUCCESS;
361 bool overwrite = false;
362 bool hex_output = false;
368 // no opertation needed
371 std::cout << "bye\n";
374 case COMMAND_FIND_HEX:
377 rval = db->Find(key, mbd);
378 if(rval == MBError::SUCCESS)
379 display_output(mbd, hex_output, false);
381 std::cout << MBError::get_error_str(rval) << "\n";
383 case COMMAND_FIND_LPREFIX_HEX:
385 case COMMAND_FIND_LPREFIX:
386 rval = db->FindLongestPrefix(key, mbd);
387 if(rval == MBError::SUCCESS)
388 display_output(mbd, hex_output, true);
390 std::cout << MBError::get_error_str(rval) << "\n";
393 if(mode & CONSTS::ACCESS_MODE_WRITER)
395 rval = db->Remove(key);
396 std::cout << MBError::get_error_str(rval) << "\n";
399 std::cout << "permission not allowed\n";
401 case COMMAND_REPLACE:
404 if(mode & CONSTS::ACCESS_MODE_WRITER)
406 rval = db->Add(key, value, overwrite);
407 std::cout << MBError::get_error_str(rval) << "\n";
410 std::cout << "permission not allowed\n";
418 case COMMAND_DELETE_ALL:
419 if(mode & CONSTS::ACCESS_MODE_WRITER)
421 rval = db->RemoveAll();
422 std::cout << MBError::get_error_str(rval) << "\n";
425 std::cout << "permission not allowed\n";
427 case COMMAND_FIND_ALL:
430 case COMMAND_RESET_N_WRITER:
431 if(mode & CONSTS::ACCESS_MODE_WRITER)
432 std::cout << "writer is running, cannot reset writer counter\n";
434 db->UpdateNumHandlers(CONSTS::ACCESS_MODE_WRITER, -1);
436 case COMMAND_RESET_N_READER:
437 db->UpdateNumHandlers(CONSTS::ACCESS_MODE_READER, -1);
439 case COMMAND_PRINT_HEADER:
442 case COMMAND_RECLAIM_RESOURCES:
443 if(mode & CONSTS::ACCESS_MODE_WRITER)
444 db->CollectResource(1, 1);
446 std::cout << "writer is not running, can not perform grabage collection" << std::endl;
448 case COMMAND_PARSING_ERROR:
450 case COMMAND_UNKNOWN:
452 std::cout << "unknown query\n";
459 static void mbclient(DB *db, int mode)
461 rl_bind_key('\t', rl_complete);
463 printf("mabain %d.%d.%d shell\n", version[0], version[1], version[2]);
464 std::cout << "database directory: " << db->GetDBDir() << "\n";
473 char* line = readline(">> ");
474 if(line == NULL) break;
481 trim_spaces(line, cmd);
484 cmd_id = parse_command(cmd, key, value);
486 RunCommand(mode, db, cmd_id, key, value);
492 static void run_query_command(DB *db, int mode, const std::string &command_str)
499 trim_spaces(command_str.c_str(), cmd);
500 if(cmd.length() == 0)
502 std::cerr << command_str << " not a valid command\n";
506 cmd_id = parse_command(cmd, key, value);
507 RunCommand(mode, db, cmd_id, key, value);
510 static void run_script(DB *db, int mode, const std::string &script_file)
512 std::ifstream script_in(script_file);
513 if(!script_in.is_open()) {
514 std::cerr << "cannot open file " << script_file << "\n";
524 while(getline(script_in, line))
526 trim_spaces(line.c_str(), cmd);
527 if(cmd.length() == 0)
529 std::cerr << line << " not a valid query\n";
533 cmd_id = parse_command(cmd, key, value);
534 std::cout << cmd << ": ";
535 RunCommand(mode, db, cmd_id, key, value);
542 int main(int argc, char *argv[])
546 signal(SIGINT, HandleSignal);
547 signal(SIGTERM, HandleSignal);
548 signal(SIGQUIT, HandleSignal);
549 signal(SIGPIPE, HandleSignal);
550 signal(SIGHUP, HandleSignal);
551 signal(SIGSEGV, HandleSignal);
552 signal(SIGUSR1, HandleSignal);
553 signal(SIGUSR2, HandleSignal);
556 sigaddset(&mask, SIGTERM);
557 sigaddset(&mask, SIGQUIT);
558 sigaddset(&mask, SIGINT);
559 sigaddset(&mask, SIGPIPE);
560 sigaddset(&mask, SIGHUP);
561 sigaddset(&mask, SIGSEGV);
562 sigaddset(&mask, SIGUSR1);
563 sigaddset(&mask, SIGUSR2);
564 pthread_sigmask(SIG_SETMASK, &mask, NULL);
567 pthread_sigmask(SIG_SETMASK, &mask, NULL);
569 int64_t memcap_i = 1024*1024LL;
570 int64_t memcap_d = 1024*1024LL;
571 const char *db_dir = NULL;
573 std::string query_cmd = "";
574 std::string script_file = "";
575 int64_t index_blk_size = 64LL*1024*1024;
576 int64_t data_blk_size = 64LL*1024*1024;
577 int64_t lru_bucket_size = 1000;
579 for(int i = 1; i < argc; i++)
581 if(strcmp(argv[i], "-d") == 0)
587 else if(strcmp(argv[i], "-im") == 0)
591 memcap_i = atoi(argv[i]);
593 else if(strcmp(argv[i], "-dm") == 0)
597 memcap_d = atoi(argv[i]);
599 else if(strcmp(argv[i], "-w") == 0)
601 mode |= CONSTS::ACCESS_MODE_WRITER;
603 else if(strcmp(argv[i], "-e") == 0)
609 else if(strcmp(argv[i], "-s") == 0)
613 script_file = argv[i];
615 else if(strcmp(argv[i], "--lru-bucket-size") == 0)
619 lru_bucket_size = atoi(argv[i]);
621 else if(strcmp(argv[i], "--index-block-size") == 0)
625 index_blk_size = atoi(argv[i]);
627 else if(strcmp(argv[i], "--data-block-size") == 0)
631 data_blk_size = atoi(argv[i]);
641 memset(&mbconf, 0, sizeof(mbconf));
642 mbconf.mbdir = db_dir;
643 mbconf.options = mode;
644 mbconf.memcap_index = memcap_i;
645 mbconf.memcap_data = memcap_d;
646 mbconf.block_size_index = index_blk_size;
647 mbconf.block_size_data = data_blk_size;
648 mbconf.num_entry_per_bucket = lru_bucket_size;
649 DB *db = new DB(mbconf);
652 std::cout << db->StatusStr() << "\n";
656 // DB::SetLogFile("/var/tmp/mabain.log");
659 if(query_cmd.length() != 0)
661 run_query_command(db, mode, query_cmd);
663 else if(script_file.length() != 0)
665 run_script(db, mode, script_file);
673 // DB::CloseLogFile();