ovsdb: Monitor support.
[cascardo/ovs.git] / ovsdb / ovsdb-client.c
1 /*
2  * Copyright (c) 2009 Nicira Networks.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <config.h>
18
19 #include <errno.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <signal.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "command-line.h"
28 #include "column.h"
29 #include "compiler.h"
30 #include "dynamic-string.h"
31 #include "json.h"
32 #include "jsonrpc.h"
33 #include "ovsdb.h"
34 #include "ovsdb-error.h"
35 #include "stream.h"
36 #include "table.h"
37 #include "timeval.h"
38 #include "util.h"
39
40 #include "vlog.h"
41 #define THIS_MODULE VLM_ovsdb_client
42
43 /* --format: Output formatting. */
44 static enum {
45     FMT_TABLE,                  /* Textual table. */
46     FMT_HTML,                   /* HTML table. */
47     FMT_CSV                     /* Comma-separated lines. */
48 } output_format;
49
50 /* --wide: For --format=table, the maximum output width. */
51 static int output_width;
52
53 /* --no-headings: Whether table output should include headings. */
54 static int output_headings = true;
55
56 static const struct command all_commands[];
57
58 static void usage(void) NO_RETURN;
59 static void parse_options(int argc, char *argv[]);
60
61 int
62 main(int argc, char *argv[])
63 {
64     set_program_name(argv[0]);
65     time_init();
66     vlog_init();
67     parse_options(argc, argv);
68     signal(SIGPIPE, SIG_IGN);
69     run_command(argc - optind, argv + optind, all_commands);
70     return 0;
71 }
72
73 static void
74 parse_options(int argc, char *argv[])
75 {
76     static struct option long_options[] = {
77         {"wide", no_argument, &output_width, INT_MAX},
78         {"format", required_argument, 0, 'f'},
79             {"no-headings", no_argument, &output_headings, 0},
80         {"verbose", optional_argument, 0, 'v'},
81         {"help", no_argument, 0, 'h'},
82         {"version", no_argument, 0, 'V'},
83         {0, 0, 0, 0},
84     };
85     char *short_options = long_options_to_short_options(long_options);
86
87     output_width = isatty(fileno(stdout)) ? 79 : INT_MAX;
88     for (;;) {
89         int c;
90
91         c = getopt_long(argc, argv, short_options, long_options, NULL);
92         if (c == -1) {
93             break;
94         }
95
96         switch (c) {
97         case 'f':
98             if (!strcmp(optarg, "table")) {
99                 output_format = FMT_TABLE;
100             } else if (!strcmp(optarg, "html")) {
101                 output_format = FMT_HTML;
102             } else if (!strcmp(optarg, "csv")) {
103                 output_format = FMT_CSV;
104             } else {
105                 ovs_fatal(0, "unknown output format \"%s\"", optarg);
106             }
107             break;
108
109         case 'w':
110             output_width = INT_MAX;
111             break;
112
113         case 'h':
114             usage();
115
116         case 'V':
117             OVS_PRINT_VERSION(0, 0);
118             exit(EXIT_SUCCESS);
119
120         case 'v':
121             vlog_set_verbosity(optarg);
122             break;
123
124         case '?':
125             exit(EXIT_FAILURE);
126
127         case 0:
128             /* getopt_long() already set the value for us. */
129             break;
130
131         default:
132             abort();
133         }
134     }
135     free(short_options);
136 }
137
138 static void
139 usage(void)
140 {
141     printf("%s: Open vSwitch database JSON-RPC client\n"
142            "usage: %s [OPTIONS] COMMAND [ARG...]\n"
143            "\nValid commands are:\n"
144            "\n  get-schema SERVER\n"
145            "    retrieve schema from SERVER\n"
146            "\n  list-tables SERVER\n"
147            "    list SERVER's tables\n"
148            "\n  list-columns SERVER [TABLE]\n"
149            "    list columns in TABLE (or all tables) on SERVER\n"
150            "\n  transact SERVER TRANSACTION\n"
151            "    run TRANSACTION (a JSON array of operations) on SERVER\n"
152            "    and print the results as JSON on stdout\n"
153            "\n  monitor SERVER TABLE [COLUMN,...] [SELECT,...]\n"
154            "    monitor contents of (COLUMNs in) TABLE on SERVER\n"
155            "    Valid SELECTs are: initial, insert, delete, modify\n",
156            program_name, program_name);
157     stream_usage("SERVER", true, true);
158     printf("\nOutput formatting options:\n"
159            "  -f, --format=FORMAT         set output formatting to FORMAT\n"
160            "                              (\"table\", \"html\", or \"csv\"\n"
161            "  --wide                      don't limit TTY lines to 79 bytes\n"
162            "  --no-headings               omit table heading row\n");
163     vlog_usage();
164     printf("\nOther options:\n"
165            "  -h, --help                  display this help message\n"
166            "  -V, --version               display version information\n");
167     exit(EXIT_SUCCESS);
168 }
169 \f
170 static struct json *
171 parse_json(const char *s)
172 {
173     struct json *json = json_from_string(s);
174     if (json->type == JSON_STRING) {
175         ovs_fatal(0, "\"%s\": %s", s, json->u.string);
176     }
177     return json;
178 }
179
180 static struct jsonrpc *
181 open_jsonrpc(const char *server)
182 {
183     struct stream *stream;
184     int error;
185
186     error = stream_open_block(server, &stream);
187     if (error == EAFNOSUPPORT) {
188         struct pstream *pstream;
189
190         error = pstream_open(server, &pstream);
191         if (error) {
192             ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
193         }
194
195         VLOG_INFO("%s: waiting for connection...", server);
196         error = pstream_accept_block(pstream, &stream);
197         if (error) {
198             ovs_fatal(error, "failed to accept connection on \"%s\"", server);
199         }
200
201         pstream_close(pstream);
202     } else if (error) {
203         ovs_fatal(error, "failed to connect to \"%s\"", server);
204     }
205
206     return jsonrpc_open(stream);
207 }
208
209 static void
210 print_json(struct json *json)
211 {
212     char *string = json_to_string(json, JSSF_SORT);
213     fputs(string, stdout);
214     free(string);
215 }
216
217 static void
218 print_and_free_json(struct json *json)
219 {
220     print_json(json);
221     json_destroy(json);
222 }
223
224 static void
225 check_ovsdb_error(struct ovsdb_error *error)
226 {
227     if (error) {
228         ovs_fatal(0, "%s", ovsdb_error_to_string(error));
229     }
230 }
231
232 static struct ovsdb_schema *
233 fetch_schema_from_rpc(struct jsonrpc *rpc)
234 {
235     struct jsonrpc_msg *request, *reply;
236     struct ovsdb_schema *schema;
237     int error;
238
239     request = jsonrpc_create_request("get_schema", json_array_create_empty());
240     error = jsonrpc_transact_block(rpc, request, &reply);
241     if (error) {
242         ovs_fatal(error, "transaction failed");
243     }
244     check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
245     jsonrpc_msg_destroy(reply);
246
247     return schema;
248 }
249
250 static struct ovsdb_schema *
251 fetch_schema(const char *server)
252 {
253     struct ovsdb_schema *schema;
254     struct jsonrpc *rpc;
255
256     rpc = open_jsonrpc(server);
257     schema = fetch_schema_from_rpc(rpc);
258     jsonrpc_close(rpc);
259
260     return schema;
261 }
262 \f
263 struct column {
264     char *heading;
265     int width;
266 };
267
268 struct table {
269     char **cells;
270     struct column *columns;
271     size_t n_columns, allocated_columns;
272     size_t n_rows, allocated_rows;
273     size_t current_column;
274 };
275
276 static void
277 table_init(struct table *table)
278 {
279     memset(table, 0, sizeof *table);
280 }
281
282 static void
283 table_destroy(struct table *table)
284 {
285     size_t i;
286
287     for (i = 0; i < table->n_columns; i++) {
288         free(table->columns[i].heading);
289     }
290     free(table->columns);
291
292     for (i = 0; i < table->n_columns * table->n_rows; i++) {
293         free(table->cells[i]);
294     }
295     free(table->cells);
296 }
297
298 static void
299 table_add_column(struct table *table, const char *heading, ...)
300     PRINTF_FORMAT(2, 3);
301
302 static void
303 table_add_column(struct table *table, const char *heading, ...)
304 {
305     struct column *column;
306     va_list args;
307
308     assert(!table->n_rows);
309     if (table->n_columns >= table->allocated_columns) {
310         table->columns = x2nrealloc(table->columns, &table->allocated_columns,
311                                     sizeof *table->columns);
312     }
313     column = &table->columns[table->n_columns++];
314
315     va_start(args, heading);
316     column->heading = xvasprintf(heading, args);
317     column->width = strlen(column->heading);
318     va_end(args);
319 }
320
321 static char **
322 table_cell__(const struct table *table, size_t row, size_t column)
323 {
324     return &table->cells[column + row * table->n_columns];
325 }
326
327 static void
328 table_add_row(struct table *table)
329 {
330     size_t x, y;
331
332     if (table->n_rows >= table->allocated_rows) {
333         table->cells = x2nrealloc(table->cells, &table->allocated_rows,
334                                   table->n_columns * sizeof *table->cells);
335     }
336
337     y = table->n_rows++;
338     table->current_column = 0;
339     for (x = 0; x < table->n_columns; x++) {
340         *table_cell__(table, y, x) = NULL;
341     }
342 }
343
344 static void
345 table_add_cell_nocopy(struct table *table, char *s)
346 {
347     size_t x, y;
348     int length;
349
350     assert(table->n_rows > 0);
351     assert(table->current_column < table->n_columns);
352
353     x = table->current_column++;
354     y = table->n_rows - 1;
355     *table_cell__(table, y, x) = s;
356
357     length = strlen(s);
358     if (length > table->columns[x].width) {
359         table->columns[x].width = length;
360     }
361 }
362
363 static void
364 table_add_cell(struct table *table, const char *format, ...)
365 {
366     va_list args;
367
368     va_start(args, format);
369     table_add_cell_nocopy(table, xvasprintf(format, args));
370     va_end(args);
371 }
372
373 static void
374 table_print_table_line__(struct ds *line, size_t max_width)
375 {
376     ds_truncate(line, max_width);
377     puts(ds_cstr(line));
378     ds_clear(line);
379 }
380
381 static void
382 table_print_table__(const struct table *table)
383 {
384     struct ds line = DS_EMPTY_INITIALIZER;
385     size_t x, y;
386
387     if (output_headings) {
388         for (x = 0; x < table->n_columns; x++) {
389             const struct column *column = &table->columns[x];
390             if (x) {
391                 ds_put_char(&line, ' ');
392             }
393             ds_put_format(&line, "%-*s", column->width, column->heading);
394         }
395         table_print_table_line__(&line, output_width);
396
397         for (x = 0; x < table->n_columns; x++) {
398             const struct column *column = &table->columns[x];
399             int i;
400
401             if (x) {
402                 ds_put_char(&line, ' ');
403             }
404             for (i = 0; i < column->width; i++) {
405                 ds_put_char(&line, '-');
406             }
407         }
408         table_print_table_line__(&line, output_width);
409     }
410
411     for (y = 0; y < table->n_rows; y++) {
412         for (x = 0; x < table->n_columns; x++) {
413             const char *cell = *table_cell__(table, y, x);
414             if (x) {
415                 ds_put_char(&line, ' ');
416             }
417             ds_put_format(&line, "%-*s", table->columns[x].width, cell);
418         }
419         table_print_table_line__(&line, output_width);
420     }
421
422     ds_destroy(&line);
423 }
424
425 static void
426 table_print_html_cell__(const char *element, const char *content)
427 {
428     const char *p;
429
430     printf("    <%s>", element);
431     for (p = content; *p != '\0'; p++) {
432         switch (*p) {
433         case '&':
434             fputs("&amp;", stdout);
435             break;
436         case '<':
437             fputs("&lt;", stdout);
438             break;
439         case '>':
440             fputs("&gt;", stdout);
441             break;
442         default:
443             putchar(*p);
444             break;
445         }
446     }
447     printf("</%s>\n", element);
448 }
449
450 static void
451 table_print_html__(const struct table *table)
452 {
453     size_t x, y;
454
455     fputs("<table>\n", stdout);
456
457     if (output_headings) {
458         fputs("  <tr>\n", stdout);
459         for (x = 0; x < table->n_columns; x++) {
460             const struct column *column = &table->columns[x];
461             table_print_html_cell__("th", column->heading);
462         }
463         fputs("  </tr>\n", stdout);
464     }
465
466     for (y = 0; y < table->n_rows; y++) {
467         fputs("  <tr>\n", stdout);
468         for (x = 0; x < table->n_columns; x++) {
469             table_print_html_cell__("td", *table_cell__(table, y, x));
470         }
471         fputs("  </tr>\n", stdout);
472     }
473
474     fputs("</table>\n", stdout);
475 }
476
477 static void
478 table_print_csv_cell__(const char *content)
479 {
480     const char *p;
481
482     if (!strpbrk(content, "\n\",")) {
483         fputs(content, stdout);
484     } else {
485         putchar('"');
486         for (p = content; *p != '\0'; p++) {
487             switch (*p) {
488             case '"':
489                 fputs("\"\"", stdout);
490                 break;
491             default:
492                 putchar(*p);
493                 break;
494             }
495         }
496         putchar('"');
497     }
498 }
499
500 static void
501 table_print_csv__(const struct table *table)
502 {
503     size_t x, y;
504
505     if (output_headings) {
506         for (x = 0; x < table->n_columns; x++) {
507             const struct column *column = &table->columns[x];
508             if (x) {
509                 putchar(',');
510             }
511             table_print_csv_cell__(column->heading);
512         }
513         putchar('\n');
514     }
515
516     for (y = 0; y < table->n_rows; y++) {
517         for (x = 0; x < table->n_columns; x++) {
518             if (x) {
519                 putchar(',');
520             }
521             table_print_csv_cell__(*table_cell__(table, y, x));
522         }
523         putchar('\n');
524     }
525 }
526
527 static void
528 table_print(const struct table *table)
529 {
530     switch (output_format) {
531     case FMT_TABLE:
532         table_print_table__(table);
533         break;
534
535     case FMT_HTML:
536         table_print_html__(table);
537         break;
538
539     case FMT_CSV:
540         table_print_csv__(table);
541         break;
542     }
543 }
544 \f
545 static void
546 do_get_schema(int argc UNUSED, char *argv[])
547 {
548     struct ovsdb_schema *schema = fetch_schema(argv[1]);
549     print_and_free_json(ovsdb_schema_to_json(schema));
550     ovsdb_schema_destroy(schema);
551 }
552
553 static void
554 do_list_tables(int argc UNUSED, char *argv[])
555 {
556     struct ovsdb_schema *schema;
557     struct shash_node *node;
558     struct table t;
559
560     schema = fetch_schema(argv[1]);
561     table_init(&t);
562     table_add_column(&t, "Table");
563     table_add_column(&t, "Comment");
564     SHASH_FOR_EACH (node, &schema->tables) {
565         struct ovsdb_table_schema *ts = node->data;
566
567         table_add_row(&t);
568         table_add_cell(&t, ts->name);
569         if (ts->comment) {
570             table_add_cell(&t, ts->comment);
571         }
572     }
573     ovsdb_schema_destroy(schema);
574     table_print(&t);
575 }
576
577 static void
578 do_list_columns(int argc UNUSED, char *argv[])
579 {
580     const char *table_name = argv[2];
581     struct ovsdb_schema *schema;
582     struct shash_node *table_node;
583     struct table t;
584
585     schema = fetch_schema(argv[1]);
586     table_init(&t);
587     if (!table_name) {
588         table_add_column(&t, "Table");
589     }
590     table_add_column(&t, "Column");
591     table_add_column(&t, "Type");
592     table_add_column(&t, "Comment");
593     SHASH_FOR_EACH (table_node, &schema->tables) {
594         struct ovsdb_table_schema *ts = table_node->data;
595
596         if (!table_name || !strcmp(table_name, ts->name)) {
597             struct shash_node *column_node;
598
599             SHASH_FOR_EACH (column_node, &ts->columns) {
600                 struct ovsdb_column *column = column_node->data;
601                 struct json *type = ovsdb_type_to_json(&column->type);
602
603                 table_add_row(&t);
604                 if (!table_name) {
605                     table_add_cell(&t, ts->name);
606                 }
607                 table_add_cell(&t, column->name);
608                 table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
609                 if (column->comment) {
610                     table_add_cell(&t, column->comment);
611                 }
612
613                 json_destroy(type);
614             }
615         }
616     }
617     ovsdb_schema_destroy(schema);
618     table_print(&t);
619 }
620
621 static void
622 do_transact(int argc UNUSED, char *argv[])
623 {
624     struct jsonrpc_msg *request, *reply;
625     struct json *transaction;
626     struct jsonrpc *rpc;
627     int error;
628
629     transaction = parse_json(argv[2]);
630
631     rpc = open_jsonrpc(argv[1]);
632     request = jsonrpc_create_request("transact", transaction);
633     error = jsonrpc_transact_block(rpc, request, &reply);
634     if (error) {
635         ovs_fatal(error, "transaction failed");
636     }
637     if (reply->error) {
638         ovs_fatal(error, "transaction returned error: %s",
639                   json_to_string(reply->error, JSSF_SORT));
640     }
641     print_json(reply->result);
642     putchar('\n');
643     jsonrpc_msg_destroy(reply);
644     jsonrpc_close(rpc);
645 }
646
647 static void
648 monitor_print_row(struct json *row, const char *type, const char *uuid,
649                   const struct ovsdb_column_set *columns, struct table *t)
650 {
651     size_t i;
652
653     if (!row) {
654         ovs_error(0, "missing %s row", type);
655         return;
656     } else if (row->type != JSON_OBJECT) {
657         ovs_error(0, "<row> is not object");
658         return;
659     }
660
661     table_add_row(t);
662     table_add_cell(t, uuid);
663     table_add_cell(t, type);
664     for (i = 0; i < columns->n_columns; i++) {
665         const struct ovsdb_column *column = columns->columns[i];
666         struct json *value = shash_find_data(json_object(row), column->name);
667         if (value) {
668             table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT));
669         } else {
670             table_add_cell(t, "");
671         }
672     }
673 }
674
675 static void
676 monitor_print(struct json *table_updates,
677               const struct ovsdb_table_schema *table,
678               const struct ovsdb_column_set *columns, bool initial)
679 {
680     struct json *table_update;
681     struct shash_node *node;
682     struct table t;
683     size_t i;
684
685     table_init(&t);
686
687     if (table_updates->type != JSON_OBJECT) {
688         ovs_error(0, "<table-updates> is not object");
689         return;
690     }
691     table_update = shash_find_data(json_object(table_updates), table->name);
692     if (!table_update) {
693         return;
694     }
695     if (table_update->type != JSON_OBJECT) {
696         ovs_error(0, "<table-update> is not object");
697         return;
698     }
699
700     table_add_column(&t, "row");
701     table_add_column(&t, "action");
702     for (i = 0; i < columns->n_columns; i++) {
703         table_add_column(&t, "%s", columns->columns[i]->name);
704     }
705     SHASH_FOR_EACH (node, json_object(table_update)) {
706         struct json *row_update = node->data;
707         struct json *old, *new;
708
709         if (row_update->type != JSON_OBJECT) {
710             ovs_error(0, "<row-update> is not object");
711             continue;
712         }
713         old = shash_find_data(json_object(row_update), "old");
714         new = shash_find_data(json_object(row_update), "new");
715         if (initial) {
716             monitor_print_row(new, "initial", node->name, columns, &t);
717         } else if (!old) {
718             monitor_print_row(new, "insert", node->name, columns, &t);
719         } else if (!new) {
720             monitor_print_row(old, "delete", node->name, columns, &t);
721         } else {
722             monitor_print_row(old, "old", node->name, columns, &t);
723             monitor_print_row(new, "new", "", columns, &t);
724         }
725     }
726     table_print(&t);
727     table_destroy(&t);
728 }
729
730 static void
731 do_monitor(int argc, char *argv[])
732 {
733     struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
734     struct ovsdb_table_schema *table;
735     struct ovsdb_schema *schema;
736     struct jsonrpc_msg *request;
737     struct jsonrpc *rpc;
738     struct json *select, *monitor, *monitor_request, *monitor_requests,
739         *request_id;
740
741     rpc = open_jsonrpc(argv[1]);
742
743     schema = fetch_schema_from_rpc(rpc);
744     table = shash_find_data(&schema->tables, argv[2]);
745     if (!table) {
746         ovs_fatal(0, "%s: no table named \"%s\"", argv[1], argv[2]);
747     }
748
749     if (argc >= 4 && *argv[3] != '\0') {
750         char *save_ptr = NULL;
751         char *token;
752
753         for (token = strtok_r(argv[3], ",", &save_ptr); token != NULL;
754              token = strtok_r(NULL, ",", &save_ptr)) {
755             const struct ovsdb_column *column;
756             column = ovsdb_table_schema_get_column(table, token);
757             if (!column) {
758                 ovs_fatal(0, "%s: table \"%s\" does not have a "
759                           "column named \"%s\"", argv[1], argv[2], token);
760             }
761             ovsdb_column_set_add(&columns, column);
762         }
763     } else {
764         struct shash_node *node;
765
766         SHASH_FOR_EACH (node, &table->columns) {
767             const struct ovsdb_column *column = node->data;
768             if (column->index != OVSDB_COL_UUID) {
769                 ovsdb_column_set_add(&columns, column);
770             }
771         }
772     }
773
774     if (argc >= 5 && *argv[4] != '\0') {
775         char *save_ptr = NULL;
776         char *token;
777
778         select = json_object_create();
779         for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
780              token = strtok_r(NULL, ",", &save_ptr)) {
781             json_object_put(select, token, json_boolean_create(true));
782         }
783     } else {
784         select = NULL;
785     }
786
787     monitor_request = json_object_create();
788     json_object_put(monitor_request,
789                     "columns", ovsdb_column_set_to_json(&columns));
790     if (select) {
791         json_object_put(monitor_request, "select", select);
792     }
793
794     monitor_requests = json_object_create();
795     json_object_put(monitor_requests, argv[2], monitor_request);
796
797     monitor = json_array_create_2(json_null_create(), monitor_requests);
798     request = jsonrpc_create_request("monitor", monitor);
799     request_id = json_clone(request->id);
800     jsonrpc_send(rpc, request);
801     for (;;) {
802         struct jsonrpc_msg *msg;
803         int error;
804
805         error = jsonrpc_recv_block(rpc, &msg);
806         if (error) {
807             ovs_fatal(error, "%s: receive failed", argv[1]);
808         }
809
810         if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
811             jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
812                                                    msg->id));
813         } else if (msg->type == JSONRPC_REPLY
814                    && json_equal(msg->id, request_id)) {
815             monitor_print(msg->result, table, &columns, true);
816         } else if (msg->type == JSONRPC_NOTIFY
817                    && !strcmp(msg->method, "update")) {
818             struct json *params = msg->params;
819             if (params->type == JSON_ARRAY
820                 && params->u.array.n == 2
821                 && params->u.array.elems[0]->type == JSON_NULL) {
822                 monitor_print(params->u.array.elems[1],
823                               table, &columns, false);
824             }
825         }
826     }
827 }
828
829 static void
830 do_help(int argc UNUSED, char *argv[] UNUSED)
831 {
832     usage();
833 }
834
835 static const struct command all_commands[] = {
836     { "get-schema", 1, 1, do_get_schema },
837     { "list-tables", 1, 1, do_list_tables },
838     { "list-columns", 1, 2, do_list_columns },
839     { "transact", 2, 2, do_transact },
840     { "monitor", 2, 4, do_monitor },
841     { "help", 0, INT_MAX, do_help },
842     { NULL, 0, 0, NULL },
843 };