Merge "master" branch into "db".
[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                                      NULL);
241     error = jsonrpc_transact_block(rpc, request, &reply);
242     if (error) {
243         ovs_fatal(error, "transaction failed");
244     }
245     check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
246     jsonrpc_msg_destroy(reply);
247
248     return schema;
249 }
250
251 static struct ovsdb_schema *
252 fetch_schema(const char *server)
253 {
254     struct ovsdb_schema *schema;
255     struct jsonrpc *rpc;
256
257     rpc = open_jsonrpc(server);
258     schema = fetch_schema_from_rpc(rpc);
259     jsonrpc_close(rpc);
260
261     return schema;
262 }
263 \f
264 struct column {
265     char *heading;
266     int width;
267 };
268
269 struct table {
270     char **cells;
271     struct column *columns;
272     size_t n_columns, allocated_columns;
273     size_t n_rows, allocated_rows;
274     size_t current_column;
275 };
276
277 static void
278 table_init(struct table *table)
279 {
280     memset(table, 0, sizeof *table);
281 }
282
283 static void
284 table_destroy(struct table *table)
285 {
286     size_t i;
287
288     for (i = 0; i < table->n_columns; i++) {
289         free(table->columns[i].heading);
290     }
291     free(table->columns);
292
293     for (i = 0; i < table->n_columns * table->n_rows; i++) {
294         free(table->cells[i]);
295     }
296     free(table->cells);
297 }
298
299 static void
300 table_add_column(struct table *table, const char *heading, ...)
301     PRINTF_FORMAT(2, 3);
302
303 static void
304 table_add_column(struct table *table, const char *heading, ...)
305 {
306     struct column *column;
307     va_list args;
308
309     assert(!table->n_rows);
310     if (table->n_columns >= table->allocated_columns) {
311         table->columns = x2nrealloc(table->columns, &table->allocated_columns,
312                                     sizeof *table->columns);
313     }
314     column = &table->columns[table->n_columns++];
315
316     va_start(args, heading);
317     column->heading = xvasprintf(heading, args);
318     column->width = strlen(column->heading);
319     va_end(args);
320 }
321
322 static char **
323 table_cell__(const struct table *table, size_t row, size_t column)
324 {
325     return &table->cells[column + row * table->n_columns];
326 }
327
328 static void
329 table_add_row(struct table *table)
330 {
331     size_t x, y;
332
333     if (table->n_rows >= table->allocated_rows) {
334         table->cells = x2nrealloc(table->cells, &table->allocated_rows,
335                                   table->n_columns * sizeof *table->cells);
336     }
337
338     y = table->n_rows++;
339     table->current_column = 0;
340     for (x = 0; x < table->n_columns; x++) {
341         *table_cell__(table, y, x) = NULL;
342     }
343 }
344
345 static void
346 table_add_cell_nocopy(struct table *table, char *s)
347 {
348     size_t x, y;
349     int length;
350
351     assert(table->n_rows > 0);
352     assert(table->current_column < table->n_columns);
353
354     x = table->current_column++;
355     y = table->n_rows - 1;
356     *table_cell__(table, y, x) = s;
357
358     length = strlen(s);
359     if (length > table->columns[x].width) {
360         table->columns[x].width = length;
361     }
362 }
363
364 static void
365 table_add_cell(struct table *table, const char *format, ...)
366 {
367     va_list args;
368
369     va_start(args, format);
370     table_add_cell_nocopy(table, xvasprintf(format, args));
371     va_end(args);
372 }
373
374 static void
375 table_print_table_line__(struct ds *line, size_t max_width)
376 {
377     ds_truncate(line, max_width);
378     puts(ds_cstr(line));
379     ds_clear(line);
380 }
381
382 static void
383 table_print_table__(const struct table *table)
384 {
385     struct ds line = DS_EMPTY_INITIALIZER;
386     size_t x, y;
387
388     if (output_headings) {
389         for (x = 0; x < table->n_columns; x++) {
390             const struct column *column = &table->columns[x];
391             if (x) {
392                 ds_put_char(&line, ' ');
393             }
394             ds_put_format(&line, "%-*s", column->width, column->heading);
395         }
396         table_print_table_line__(&line, output_width);
397
398         for (x = 0; x < table->n_columns; x++) {
399             const struct column *column = &table->columns[x];
400             int i;
401
402             if (x) {
403                 ds_put_char(&line, ' ');
404             }
405             for (i = 0; i < column->width; i++) {
406                 ds_put_char(&line, '-');
407             }
408         }
409         table_print_table_line__(&line, output_width);
410     }
411
412     for (y = 0; y < table->n_rows; y++) {
413         for (x = 0; x < table->n_columns; x++) {
414             const char *cell = *table_cell__(table, y, x);
415             if (x) {
416                 ds_put_char(&line, ' ');
417             }
418             ds_put_format(&line, "%-*s", table->columns[x].width, cell);
419         }
420         table_print_table_line__(&line, output_width);
421     }
422
423     ds_destroy(&line);
424 }
425
426 static void
427 table_print_html_cell__(const char *element, const char *content)
428 {
429     const char *p;
430
431     printf("    <%s>", element);
432     for (p = content; *p != '\0'; p++) {
433         switch (*p) {
434         case '&':
435             fputs("&amp;", stdout);
436             break;
437         case '<':
438             fputs("&lt;", stdout);
439             break;
440         case '>':
441             fputs("&gt;", stdout);
442             break;
443         default:
444             putchar(*p);
445             break;
446         }
447     }
448     printf("</%s>\n", element);
449 }
450
451 static void
452 table_print_html__(const struct table *table)
453 {
454     size_t x, y;
455
456     fputs("<table>\n", stdout);
457
458     if (output_headings) {
459         fputs("  <tr>\n", stdout);
460         for (x = 0; x < table->n_columns; x++) {
461             const struct column *column = &table->columns[x];
462             table_print_html_cell__("th", column->heading);
463         }
464         fputs("  </tr>\n", stdout);
465     }
466
467     for (y = 0; y < table->n_rows; y++) {
468         fputs("  <tr>\n", stdout);
469         for (x = 0; x < table->n_columns; x++) {
470             table_print_html_cell__("td", *table_cell__(table, y, x));
471         }
472         fputs("  </tr>\n", stdout);
473     }
474
475     fputs("</table>\n", stdout);
476 }
477
478 static void
479 table_print_csv_cell__(const char *content)
480 {
481     const char *p;
482
483     if (!strpbrk(content, "\n\",")) {
484         fputs(content, stdout);
485     } else {
486         putchar('"');
487         for (p = content; *p != '\0'; p++) {
488             switch (*p) {
489             case '"':
490                 fputs("\"\"", stdout);
491                 break;
492             default:
493                 putchar(*p);
494                 break;
495             }
496         }
497         putchar('"');
498     }
499 }
500
501 static void
502 table_print_csv__(const struct table *table)
503 {
504     size_t x, y;
505
506     if (output_headings) {
507         for (x = 0; x < table->n_columns; x++) {
508             const struct column *column = &table->columns[x];
509             if (x) {
510                 putchar(',');
511             }
512             table_print_csv_cell__(column->heading);
513         }
514         putchar('\n');
515     }
516
517     for (y = 0; y < table->n_rows; y++) {
518         for (x = 0; x < table->n_columns; x++) {
519             if (x) {
520                 putchar(',');
521             }
522             table_print_csv_cell__(*table_cell__(table, y, x));
523         }
524         putchar('\n');
525     }
526 }
527
528 static void
529 table_print(const struct table *table)
530 {
531     switch (output_format) {
532     case FMT_TABLE:
533         table_print_table__(table);
534         break;
535
536     case FMT_HTML:
537         table_print_html__(table);
538         break;
539
540     case FMT_CSV:
541         table_print_csv__(table);
542         break;
543     }
544 }
545 \f
546 static void
547 do_get_schema(int argc UNUSED, char *argv[])
548 {
549     struct ovsdb_schema *schema = fetch_schema(argv[1]);
550     print_and_free_json(ovsdb_schema_to_json(schema));
551     ovsdb_schema_destroy(schema);
552 }
553
554 static void
555 do_list_tables(int argc UNUSED, char *argv[])
556 {
557     struct ovsdb_schema *schema;
558     struct shash_node *node;
559     struct table t;
560
561     schema = fetch_schema(argv[1]);
562     table_init(&t);
563     table_add_column(&t, "Table");
564     table_add_column(&t, "Comment");
565     SHASH_FOR_EACH (node, &schema->tables) {
566         struct ovsdb_table_schema *ts = node->data;
567
568         table_add_row(&t);
569         table_add_cell(&t, ts->name);
570         if (ts->comment) {
571             table_add_cell(&t, ts->comment);
572         }
573     }
574     ovsdb_schema_destroy(schema);
575     table_print(&t);
576 }
577
578 static void
579 do_list_columns(int argc UNUSED, char *argv[])
580 {
581     const char *table_name = argv[2];
582     struct ovsdb_schema *schema;
583     struct shash_node *table_node;
584     struct table t;
585
586     schema = fetch_schema(argv[1]);
587     table_init(&t);
588     if (!table_name) {
589         table_add_column(&t, "Table");
590     }
591     table_add_column(&t, "Column");
592     table_add_column(&t, "Type");
593     table_add_column(&t, "Comment");
594     SHASH_FOR_EACH (table_node, &schema->tables) {
595         struct ovsdb_table_schema *ts = table_node->data;
596
597         if (!table_name || !strcmp(table_name, ts->name)) {
598             struct shash_node *column_node;
599
600             SHASH_FOR_EACH (column_node, &ts->columns) {
601                 struct ovsdb_column *column = column_node->data;
602                 struct json *type = ovsdb_type_to_json(&column->type);
603
604                 table_add_row(&t);
605                 if (!table_name) {
606                     table_add_cell(&t, ts->name);
607                 }
608                 table_add_cell(&t, column->name);
609                 table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
610                 if (column->comment) {
611                     table_add_cell(&t, column->comment);
612                 }
613
614                 json_destroy(type);
615             }
616         }
617     }
618     ovsdb_schema_destroy(schema);
619     table_print(&t);
620 }
621
622 static void
623 do_transact(int argc UNUSED, char *argv[])
624 {
625     struct jsonrpc_msg *request, *reply;
626     struct json *transaction;
627     struct jsonrpc *rpc;
628     int error;
629
630     transaction = parse_json(argv[2]);
631
632     rpc = open_jsonrpc(argv[1]);
633     request = jsonrpc_create_request("transact", transaction, NULL);
634     error = jsonrpc_transact_block(rpc, request, &reply);
635     if (error) {
636         ovs_fatal(error, "transaction failed");
637     }
638     if (reply->error) {
639         ovs_fatal(error, "transaction returned error: %s",
640                   json_to_string(reply->error, JSSF_SORT));
641     }
642     print_json(reply->result);
643     putchar('\n');
644     jsonrpc_msg_destroy(reply);
645     jsonrpc_close(rpc);
646 }
647
648 static void
649 monitor_print_row(struct json *row, const char *type, const char *uuid,
650                   const struct ovsdb_column_set *columns, struct table *t)
651 {
652     size_t i;
653
654     if (!row) {
655         ovs_error(0, "missing %s row", type);
656         return;
657     } else if (row->type != JSON_OBJECT) {
658         ovs_error(0, "<row> is not object");
659         return;
660     }
661
662     table_add_row(t);
663     table_add_cell(t, uuid);
664     table_add_cell(t, type);
665     for (i = 0; i < columns->n_columns; i++) {
666         const struct ovsdb_column *column = columns->columns[i];
667         struct json *value = shash_find_data(json_object(row), column->name);
668         if (value) {
669             table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT));
670         } else {
671             table_add_cell(t, "");
672         }
673     }
674 }
675
676 static void
677 monitor_print(struct json *table_updates,
678               const struct ovsdb_table_schema *table,
679               const struct ovsdb_column_set *columns, bool initial)
680 {
681     struct json *table_update;
682     struct shash_node *node;
683     struct table t;
684     size_t i;
685
686     table_init(&t);
687
688     if (table_updates->type != JSON_OBJECT) {
689         ovs_error(0, "<table-updates> is not object");
690         return;
691     }
692     table_update = shash_find_data(json_object(table_updates), table->name);
693     if (!table_update) {
694         return;
695     }
696     if (table_update->type != JSON_OBJECT) {
697         ovs_error(0, "<table-update> is not object");
698         return;
699     }
700
701     table_add_column(&t, "row");
702     table_add_column(&t, "action");
703     for (i = 0; i < columns->n_columns; i++) {
704         table_add_column(&t, "%s", columns->columns[i]->name);
705     }
706     SHASH_FOR_EACH (node, json_object(table_update)) {
707         struct json *row_update = node->data;
708         struct json *old, *new;
709
710         if (row_update->type != JSON_OBJECT) {
711             ovs_error(0, "<row-update> is not object");
712             continue;
713         }
714         old = shash_find_data(json_object(row_update), "old");
715         new = shash_find_data(json_object(row_update), "new");
716         if (initial) {
717             monitor_print_row(new, "initial", node->name, columns, &t);
718         } else if (!old) {
719             monitor_print_row(new, "insert", node->name, columns, &t);
720         } else if (!new) {
721             monitor_print_row(old, "delete", node->name, columns, &t);
722         } else {
723             monitor_print_row(old, "old", node->name, columns, &t);
724             monitor_print_row(new, "new", "", columns, &t);
725         }
726     }
727     table_print(&t);
728     table_destroy(&t);
729 }
730
731 static void
732 do_monitor(int argc, char *argv[])
733 {
734     struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
735     struct ovsdb_table_schema *table;
736     struct ovsdb_schema *schema;
737     struct jsonrpc_msg *request;
738     struct jsonrpc *rpc;
739     struct json *select, *monitor, *monitor_request, *monitor_requests,
740         *request_id;
741
742     rpc = open_jsonrpc(argv[1]);
743
744     schema = fetch_schema_from_rpc(rpc);
745     table = shash_find_data(&schema->tables, argv[2]);
746     if (!table) {
747         ovs_fatal(0, "%s: no table named \"%s\"", argv[1], argv[2]);
748     }
749
750     if (argc >= 4 && *argv[3] != '\0') {
751         char *save_ptr = NULL;
752         char *token;
753
754         for (token = strtok_r(argv[3], ",", &save_ptr); token != NULL;
755              token = strtok_r(NULL, ",", &save_ptr)) {
756             const struct ovsdb_column *column;
757             column = ovsdb_table_schema_get_column(table, token);
758             if (!column) {
759                 ovs_fatal(0, "%s: table \"%s\" does not have a "
760                           "column named \"%s\"", argv[1], argv[2], token);
761             }
762             ovsdb_column_set_add(&columns, column);
763         }
764     } else {
765         struct shash_node *node;
766
767         SHASH_FOR_EACH (node, &table->columns) {
768             const struct ovsdb_column *column = node->data;
769             if (column->index != OVSDB_COL_UUID) {
770                 ovsdb_column_set_add(&columns, column);
771             }
772         }
773     }
774
775     if (argc >= 5 && *argv[4] != '\0') {
776         char *save_ptr = NULL;
777         char *token;
778
779         select = json_object_create();
780         for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
781              token = strtok_r(NULL, ",", &save_ptr)) {
782             json_object_put(select, token, json_boolean_create(true));
783         }
784     } else {
785         select = NULL;
786     }
787
788     monitor_request = json_object_create();
789     json_object_put(monitor_request,
790                     "columns", ovsdb_column_set_to_json(&columns));
791     if (select) {
792         json_object_put(monitor_request, "select", select);
793     }
794
795     monitor_requests = json_object_create();
796     json_object_put(monitor_requests, argv[2], monitor_request);
797
798     monitor = json_array_create_2(json_null_create(), monitor_requests);
799     request = jsonrpc_create_request("monitor", monitor, NULL);
800     request_id = json_clone(request->id);
801     jsonrpc_send(rpc, request);
802     for (;;) {
803         struct jsonrpc_msg *msg;
804         int error;
805
806         error = jsonrpc_recv_block(rpc, &msg);
807         if (error) {
808             ovs_fatal(error, "%s: receive failed", argv[1]);
809         }
810
811         if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
812             jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
813                                                    msg->id));
814         } else if (msg->type == JSONRPC_REPLY
815                    && json_equal(msg->id, request_id)) {
816             monitor_print(msg->result, table, &columns, true);
817         } else if (msg->type == JSONRPC_NOTIFY
818                    && !strcmp(msg->method, "update")) {
819             struct json *params = msg->params;
820             if (params->type == JSON_ARRAY
821                 && params->u.array.n == 2
822                 && params->u.array.elems[0]->type == JSON_NULL) {
823                 monitor_print(params->u.array.elems[1],
824                               table, &columns, false);
825             }
826         }
827     }
828 }
829
830 static void
831 do_help(int argc UNUSED, char *argv[] UNUSED)
832 {
833     usage();
834 }
835
836 static const struct command all_commands[] = {
837     { "get-schema", 1, 1, do_get_schema },
838     { "list-tables", 1, 1, do_list_tables },
839     { "list-columns", 1, 2, do_list_columns },
840     { "transact", 2, 2, do_transact },
841     { "monitor", 2, 4, do_monitor },
842     { "help", 0, INT_MAX, do_help },
843     { NULL, 0, 0, NULL },
844 };