]>
Commit | Line | Data |
---|---|---|
d0632593 | 1 | /* |
eb8d3ed6 | 2 | * Copyright (c) 2009, 2010 Nicira Networks. |
d0632593 BP |
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 | ||
10849616 | 19 | #include <assert.h> |
d0632593 BP |
20 | #include <errno.h> |
21 | #include <getopt.h> | |
22 | #include <limits.h> | |
23 | #include <signal.h> | |
24 | #include <stdlib.h> | |
25 | #include <string.h> | |
26 | #include <unistd.h> | |
27 | ||
28 | #include "command-line.h" | |
29 | #include "column.h" | |
30 | #include "compiler.h" | |
3f262d7d | 31 | #include "daemon.h" |
d0632593 BP |
32 | #include "dynamic-string.h" |
33 | #include "json.h" | |
34 | #include "jsonrpc.h" | |
35 | #include "ovsdb.h" | |
c3a0bfd5 | 36 | #include "ovsdb-data.h" |
d0632593 | 37 | #include "ovsdb-error.h" |
25c269ef | 38 | #include "sort.h" |
d0632593 | 39 | #include "stream.h" |
9467fe62 | 40 | #include "stream-ssl.h" |
d0632593 BP |
41 | #include "table.h" |
42 | #include "timeval.h" | |
43 | #include "util.h" | |
d0632593 | 44 | #include "vlog.h" |
5136ce49 | 45 | |
d98e6007 | 46 | VLOG_DEFINE_THIS_MODULE(ovsdb_client); |
d0632593 BP |
47 | |
48 | /* --format: Output formatting. */ | |
49 | static enum { | |
50 | FMT_TABLE, /* Textual table. */ | |
51 | FMT_HTML, /* HTML table. */ | |
ee890a61 BP |
52 | FMT_CSV, /* Comma-separated lines. */ |
53 | FMT_JSON /* JSON. */ | |
d0632593 BP |
54 | } output_format; |
55 | ||
d0632593 BP |
56 | /* --no-headings: Whether table output should include headings. */ |
57 | static int output_headings = true; | |
58 | ||
b87fde85 BP |
59 | /* --pretty: Flags to pass to json_to_string(). */ |
60 | static int json_flags = JSSF_SORT; | |
61 | ||
c3a0bfd5 BP |
62 | /* --data: Format of data in output tables. */ |
63 | static enum { | |
64 | DF_STRING, /* String format. */ | |
65 | DF_JSON, /* JSON. */ | |
66 | } data_format; | |
67 | ||
d0632593 BP |
68 | static const struct command all_commands[]; |
69 | ||
70 | static void usage(void) NO_RETURN; | |
71 | static void parse_options(int argc, char *argv[]); | |
72 | ||
73 | int | |
74 | main(int argc, char *argv[]) | |
75 | { | |
40f0707c | 76 | proctitle_init(argc, argv); |
d0632593 | 77 | set_program_name(argv[0]); |
d0632593 BP |
78 | parse_options(argc, argv); |
79 | signal(SIGPIPE, SIG_IGN); | |
80 | run_command(argc - optind, argv + optind, all_commands); | |
81 | return 0; | |
82 | } | |
83 | ||
84 | static void | |
85 | parse_options(int argc, char *argv[]) | |
86 | { | |
9467fe62 | 87 | enum { |
8274ae95 BP |
88 | OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1, |
89 | DAEMON_OPTION_ENUMS, | |
9467fe62 | 90 | }; |
d0632593 | 91 | static struct option long_options[] = { |
d0632593 | 92 | {"format", required_argument, 0, 'f'}, |
c3a0bfd5 BP |
93 | {"data", required_argument, 0, 'd'}, |
94 | {"no-headings", no_argument, &output_headings, 0}, | |
b87fde85 | 95 | {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT}, |
d0632593 BP |
96 | {"verbose", optional_argument, 0, 'v'}, |
97 | {"help", no_argument, 0, 'h'}, | |
98 | {"version", no_argument, 0, 'V'}, | |
3f262d7d | 99 | DAEMON_LONG_OPTIONS, |
9467fe62 BP |
100 | #ifdef HAVE_OPENSSL |
101 | {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, | |
102 | STREAM_SSL_LONG_OPTIONS | |
103 | #endif | |
d0632593 BP |
104 | {0, 0, 0, 0}, |
105 | }; | |
106 | char *short_options = long_options_to_short_options(long_options); | |
107 | ||
d0632593 BP |
108 | for (;;) { |
109 | int c; | |
110 | ||
111 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
112 | if (c == -1) { | |
113 | break; | |
114 | } | |
115 | ||
116 | switch (c) { | |
117 | case 'f': | |
118 | if (!strcmp(optarg, "table")) { | |
119 | output_format = FMT_TABLE; | |
120 | } else if (!strcmp(optarg, "html")) { | |
121 | output_format = FMT_HTML; | |
122 | } else if (!strcmp(optarg, "csv")) { | |
123 | output_format = FMT_CSV; | |
ee890a61 BP |
124 | } else if (!strcmp(optarg, "json")) { |
125 | output_format = FMT_JSON; | |
d0632593 BP |
126 | } else { |
127 | ovs_fatal(0, "unknown output format \"%s\"", optarg); | |
128 | } | |
129 | break; | |
130 | ||
c3a0bfd5 BP |
131 | case 'd': |
132 | if (!strcmp(optarg, "string")) { | |
133 | data_format = DF_STRING; | |
134 | } else if (!strcmp(optarg, "json")) { | |
135 | data_format = DF_JSON; | |
136 | } else { | |
137 | ovs_fatal(0, "unknown data format \"%s\"", optarg); | |
138 | } | |
139 | break; | |
140 | ||
d0632593 BP |
141 | case 'h': |
142 | usage(); | |
143 | ||
144 | case 'V': | |
145 | OVS_PRINT_VERSION(0, 0); | |
146 | exit(EXIT_SUCCESS); | |
147 | ||
148 | case 'v': | |
149 | vlog_set_verbosity(optarg); | |
150 | break; | |
151 | ||
3f262d7d BP |
152 | DAEMON_OPTION_HANDLERS |
153 | ||
9467fe62 BP |
154 | #ifdef HAVE_OPENSSL |
155 | STREAM_SSL_OPTION_HANDLERS | |
156 | ||
157 | case OPT_BOOTSTRAP_CA_CERT: | |
158 | stream_ssl_set_ca_cert_file(optarg, true); | |
159 | break; | |
160 | #endif | |
161 | ||
d0632593 BP |
162 | case '?': |
163 | exit(EXIT_FAILURE); | |
164 | ||
165 | case 0: | |
166 | /* getopt_long() already set the value for us. */ | |
167 | break; | |
168 | ||
169 | default: | |
170 | abort(); | |
171 | } | |
172 | } | |
173 | free(short_options); | |
174 | } | |
175 | ||
176 | static void | |
177 | usage(void) | |
178 | { | |
179 | printf("%s: Open vSwitch database JSON-RPC client\n" | |
180 | "usage: %s [OPTIONS] COMMAND [ARG...]\n" | |
181 | "\nValid commands are:\n" | |
9cb53f26 BP |
182 | "\n list-dbs SERVER\n" |
183 | " list databases available on SERVER\n" | |
184 | "\n get-schema SERVER DATABASE\n" | |
185 | " retrieve schema for DATABASE from SERVER\n" | |
8159b984 BP |
186 | "\n get-schema-version SERVER DATABASE\n" |
187 | " retrieve schema for DATABASE from SERVER and report only its\n" | |
188 | " version number on stdout\n" | |
25c269ef BP |
189 | "\n list-tables SERVER DATABASE\n" |
190 | " list tables for DATABASE on SERVER\n" | |
9cb53f26 BP |
191 | "\n list-columns SERVER DATABASE [TABLE]\n" |
192 | " list columns in TABLE (or all tables) in DATABASE on SERVER\n" | |
6d65eee8 BP |
193 | "\n transact SERVER TRANSACTION\n" |
194 | " run TRANSACTION (a JSON array of operations) on SERVER\n" | |
a8425c53 | 195 | " and print the results as JSON on stdout\n" |
20aa445d BP |
196 | "\n monitor SERVER DATABASE TABLE [COLUMN,...]...\n" |
197 | " monitor contents of COLUMNs in TABLE in DATABASE on SERVER.\n" | |
198 | " COLUMNs may include !initial, !insert, !delete, !modify\n" | |
199 | " to avoid seeing the specified kinds of changes.\n" | |
25c269ef BP |
200 | "\n dump SERVER DATABASE\n" |
201 | " dump contents of DATABASE on SERVER to stdout\n", | |
d0632593 | 202 | program_name, program_name); |
9467fe62 | 203 | stream_usage("SERVER", true, true, true); |
d0632593 BP |
204 | printf("\nOutput formatting options:\n" |
205 | " -f, --format=FORMAT set output formatting to FORMAT\n" | |
ee890a61 BP |
206 | " (\"table\", \"html\", \"csv\", " |
207 | "or \"json\")\n" | |
b87fde85 BP |
208 | " --no-headings omit table heading row\n" |
209 | " --pretty pretty-print JSON in output"); | |
3f262d7d | 210 | daemon_usage(); |
d0632593 BP |
211 | vlog_usage(); |
212 | printf("\nOther options:\n" | |
213 | " -h, --help display this help message\n" | |
214 | " -V, --version display version information\n"); | |
215 | exit(EXIT_SUCCESS); | |
216 | } | |
217 | \f | |
6d65eee8 BP |
218 | static struct json * |
219 | parse_json(const char *s) | |
220 | { | |
221 | struct json *json = json_from_string(s); | |
222 | if (json->type == JSON_STRING) { | |
223 | ovs_fatal(0, "\"%s\": %s", s, json->u.string); | |
224 | } | |
225 | return json; | |
226 | } | |
227 | ||
d0632593 BP |
228 | static struct jsonrpc * |
229 | open_jsonrpc(const char *server) | |
230 | { | |
231 | struct stream *stream; | |
232 | int error; | |
233 | ||
0d11f523 | 234 | error = stream_open_block(jsonrpc_stream_open(server, &stream), &stream); |
1b0f0f17 BP |
235 | if (error == EAFNOSUPPORT) { |
236 | struct pstream *pstream; | |
237 | ||
0d11f523 | 238 | error = jsonrpc_pstream_open(server, &pstream); |
1b0f0f17 BP |
239 | if (error) { |
240 | ovs_fatal(error, "failed to connect or listen to \"%s\"", server); | |
241 | } | |
242 | ||
243 | VLOG_INFO("%s: waiting for connection...", server); | |
244 | error = pstream_accept_block(pstream, &stream); | |
245 | if (error) { | |
246 | ovs_fatal(error, "failed to accept connection on \"%s\"", server); | |
247 | } | |
248 | ||
249 | pstream_close(pstream); | |
250 | } else if (error) { | |
d0632593 BP |
251 | ovs_fatal(error, "failed to connect to \"%s\"", server); |
252 | } | |
253 | ||
254 | return jsonrpc_open(stream); | |
255 | } | |
256 | ||
257 | static void | |
258 | print_json(struct json *json) | |
259 | { | |
b87fde85 | 260 | char *string = json_to_string(json, json_flags); |
d0632593 BP |
261 | fputs(string, stdout); |
262 | free(string); | |
263 | } | |
264 | ||
265 | static void | |
266 | print_and_free_json(struct json *json) | |
267 | { | |
268 | print_json(json); | |
269 | json_destroy(json); | |
270 | } | |
271 | ||
272 | static void | |
273 | check_ovsdb_error(struct ovsdb_error *error) | |
274 | { | |
275 | if (error) { | |
276 | ovs_fatal(0, "%s", ovsdb_error_to_string(error)); | |
277 | } | |
278 | } | |
279 | ||
280 | static struct ovsdb_schema * | |
9cb53f26 | 281 | fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database) |
d0632593 BP |
282 | { |
283 | struct jsonrpc_msg *request, *reply; | |
284 | struct ovsdb_schema *schema; | |
d0632593 BP |
285 | int error; |
286 | ||
9cb53f26 BP |
287 | request = jsonrpc_create_request("get_schema", |
288 | json_array_create_1( | |
289 | json_string_create(database)), | |
20bed8be | 290 | NULL); |
d0632593 BP |
291 | error = jsonrpc_transact_block(rpc, request, &reply); |
292 | if (error) { | |
293 | ovs_fatal(error, "transaction failed"); | |
294 | } | |
295 | check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema)); | |
296 | jsonrpc_msg_destroy(reply); | |
a8425c53 BP |
297 | |
298 | return schema; | |
299 | } | |
300 | ||
301 | static struct ovsdb_schema * | |
9cb53f26 | 302 | fetch_schema(const char *server, const char *database) |
a8425c53 BP |
303 | { |
304 | struct ovsdb_schema *schema; | |
305 | struct jsonrpc *rpc; | |
306 | ||
307 | rpc = open_jsonrpc(server); | |
9cb53f26 | 308 | schema = fetch_schema_from_rpc(rpc, database); |
d0632593 BP |
309 | jsonrpc_close(rpc); |
310 | ||
311 | return schema; | |
312 | } | |
313 | \f | |
314 | struct column { | |
315 | char *heading; | |
d0632593 BP |
316 | }; |
317 | ||
772387d5 BP |
318 | struct cell { |
319 | /* Literal text. */ | |
320 | char *text; | |
321 | ||
322 | /* JSON. */ | |
323 | struct json *json; | |
324 | const struct ovsdb_type *type; | |
325 | }; | |
326 | ||
327 | static const char * | |
328 | cell_to_text(const struct cell *cell_) | |
329 | { | |
330 | struct cell *cell = (struct cell *) cell_; | |
331 | if (!cell->text) { | |
332 | if (cell->json) { | |
333 | if (data_format == DF_JSON || !cell->type) { | |
334 | cell->text = json_to_string(cell->json, JSSF_SORT); | |
335 | } else if (data_format == DF_STRING) { | |
336 | struct ovsdb_datum datum; | |
337 | struct ovsdb_error *error; | |
338 | struct ds s; | |
339 | ||
340 | error = ovsdb_datum_from_json(&datum, cell->type, cell->json, | |
341 | NULL); | |
342 | if (!error) { | |
343 | ds_init(&s); | |
344 | ovsdb_datum_to_string(&datum, cell->type, &s); | |
345 | ovsdb_datum_destroy(&datum, cell->type); | |
346 | cell->text = ds_steal_cstr(&s); | |
347 | } else { | |
348 | cell->text = json_to_string(cell->json, JSSF_SORT); | |
349 | } | |
350 | } else { | |
351 | NOT_REACHED(); | |
352 | } | |
353 | } else { | |
354 | cell->text = xstrdup(""); | |
355 | } | |
356 | } | |
357 | ||
358 | return cell->text; | |
359 | } | |
360 | ||
361 | static void | |
362 | cell_destroy(struct cell *cell) | |
363 | { | |
364 | free(cell->text); | |
365 | json_destroy(cell->json); | |
366 | } | |
367 | ||
d0632593 | 368 | struct table { |
772387d5 | 369 | struct cell *cells; |
d0632593 BP |
370 | struct column *columns; |
371 | size_t n_columns, allocated_columns; | |
372 | size_t n_rows, allocated_rows; | |
373 | size_t current_column; | |
25c269ef | 374 | char *caption; |
d0632593 BP |
375 | }; |
376 | ||
377 | static void | |
378 | table_init(struct table *table) | |
379 | { | |
380 | memset(table, 0, sizeof *table); | |
381 | } | |
382 | ||
a8425c53 BP |
383 | static void |
384 | table_destroy(struct table *table) | |
385 | { | |
386 | size_t i; | |
387 | ||
388 | for (i = 0; i < table->n_columns; i++) { | |
389 | free(table->columns[i].heading); | |
390 | } | |
391 | free(table->columns); | |
392 | ||
393 | for (i = 0; i < table->n_columns * table->n_rows; i++) { | |
772387d5 | 394 | cell_destroy(&table->cells[i]); |
a8425c53 BP |
395 | } |
396 | free(table->cells); | |
25c269ef BP |
397 | |
398 | free(table->caption); | |
399 | } | |
400 | ||
401 | static void | |
402 | table_set_caption(struct table *table, char *caption) | |
403 | { | |
404 | free(table->caption); | |
405 | table->caption = caption; | |
a8425c53 BP |
406 | } |
407 | ||
d0632593 BP |
408 | static void |
409 | table_add_column(struct table *table, const char *heading, ...) | |
410 | PRINTF_FORMAT(2, 3); | |
411 | ||
412 | static void | |
413 | table_add_column(struct table *table, const char *heading, ...) | |
414 | { | |
415 | struct column *column; | |
416 | va_list args; | |
417 | ||
418 | assert(!table->n_rows); | |
419 | if (table->n_columns >= table->allocated_columns) { | |
420 | table->columns = x2nrealloc(table->columns, &table->allocated_columns, | |
421 | sizeof *table->columns); | |
422 | } | |
423 | column = &table->columns[table->n_columns++]; | |
424 | ||
425 | va_start(args, heading); | |
426 | column->heading = xvasprintf(heading, args); | |
d0632593 BP |
427 | va_end(args); |
428 | } | |
429 | ||
772387d5 | 430 | static struct cell * |
d0632593 BP |
431 | table_cell__(const struct table *table, size_t row, size_t column) |
432 | { | |
433 | return &table->cells[column + row * table->n_columns]; | |
434 | } | |
435 | ||
436 | static void | |
437 | table_add_row(struct table *table) | |
438 | { | |
439 | size_t x, y; | |
440 | ||
441 | if (table->n_rows >= table->allocated_rows) { | |
442 | table->cells = x2nrealloc(table->cells, &table->allocated_rows, | |
443 | table->n_columns * sizeof *table->cells); | |
444 | } | |
445 | ||
446 | y = table->n_rows++; | |
447 | table->current_column = 0; | |
448 | for (x = 0; x < table->n_columns; x++) { | |
772387d5 BP |
449 | struct cell *cell = table_cell__(table, y, x); |
450 | memset(cell, 0, sizeof *cell); | |
d0632593 BP |
451 | } |
452 | } | |
453 | ||
772387d5 BP |
454 | static struct cell * |
455 | table_add_cell(struct table *table) | |
d0632593 BP |
456 | { |
457 | size_t x, y; | |
d0632593 BP |
458 | |
459 | assert(table->n_rows > 0); | |
460 | assert(table->current_column < table->n_columns); | |
461 | ||
462 | x = table->current_column++; | |
463 | y = table->n_rows - 1; | |
d0632593 | 464 | |
772387d5 | 465 | return table_cell__(table, y, x); |
d0632593 BP |
466 | } |
467 | ||
468 | static void | |
0c65cde9 | 469 | table_print_table_line__(struct ds *line) |
d0632593 | 470 | { |
d0632593 BP |
471 | puts(ds_cstr(line)); |
472 | ds_clear(line); | |
473 | } | |
474 | ||
475 | static void | |
476 | table_print_table__(const struct table *table) | |
477 | { | |
e5125481 | 478 | static int n = 0; |
d0632593 | 479 | struct ds line = DS_EMPTY_INITIALIZER; |
772387d5 | 480 | int *widths; |
d0632593 BP |
481 | size_t x, y; |
482 | ||
e5125481 BP |
483 | if (n++ > 0) { |
484 | putchar('\n'); | |
485 | } | |
486 | ||
06036898 BP |
487 | if (table->caption) { |
488 | puts(table->caption); | |
489 | } | |
490 | ||
772387d5 BP |
491 | widths = xmalloc(table->n_columns * sizeof *widths); |
492 | for (x = 0; x < table->n_columns; x++) { | |
493 | const struct column *column = &table->columns[x]; | |
494 | ||
495 | widths[x] = strlen(column->heading); | |
496 | for (y = 0; y < table->n_rows; y++) { | |
497 | const char *text = cell_to_text(table_cell__(table, y, x)); | |
498 | size_t length = strlen(text); | |
499 | ||
500 | if (length > widths[x]) | |
501 | widths[x] = length; | |
502 | } | |
503 | } | |
504 | ||
d0632593 BP |
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 | ds_put_char(&line, ' '); | |
510 | } | |
772387d5 | 511 | ds_put_format(&line, "%-*s", widths[x], column->heading); |
d0632593 | 512 | } |
0c65cde9 | 513 | table_print_table_line__(&line); |
d0632593 BP |
514 | |
515 | for (x = 0; x < table->n_columns; x++) { | |
d0632593 BP |
516 | if (x) { |
517 | ds_put_char(&line, ' '); | |
518 | } | |
772387d5 | 519 | ds_put_char_multiple(&line, '-', widths[x]); |
d0632593 | 520 | } |
0c65cde9 | 521 | table_print_table_line__(&line); |
d0632593 BP |
522 | } |
523 | ||
524 | for (y = 0; y < table->n_rows; y++) { | |
525 | for (x = 0; x < table->n_columns; x++) { | |
772387d5 | 526 | const char *text = cell_to_text(table_cell__(table, y, x)); |
d0632593 BP |
527 | if (x) { |
528 | ds_put_char(&line, ' '); | |
529 | } | |
772387d5 | 530 | ds_put_format(&line, "%-*s", widths[x], text); |
d0632593 | 531 | } |
0c65cde9 | 532 | table_print_table_line__(&line); |
d0632593 BP |
533 | } |
534 | ||
535 | ds_destroy(&line); | |
772387d5 | 536 | free(widths); |
d0632593 BP |
537 | } |
538 | ||
539 | static void | |
1f441586 | 540 | table_escape_html_text__(const char *s, size_t n) |
d0632593 | 541 | { |
1f441586 | 542 | size_t i; |
d0632593 | 543 | |
1f441586 BP |
544 | for (i = 0; i < n; i++) { |
545 | char c = s[i]; | |
546 | ||
547 | switch (c) { | |
d0632593 BP |
548 | case '&': |
549 | fputs("&", stdout); | |
550 | break; | |
551 | case '<': | |
552 | fputs("<", stdout); | |
553 | break; | |
554 | case '>': | |
555 | fputs(">", stdout); | |
556 | break; | |
1f441586 BP |
557 | case '"': |
558 | fputs(""", stdout); | |
559 | break; | |
d0632593 | 560 | default: |
1f441586 | 561 | putchar(c); |
d0632593 BP |
562 | break; |
563 | } | |
564 | } | |
1f441586 BP |
565 | } |
566 | ||
567 | static void | |
568 | table_print_html_cell__(const char *element, const char *content) | |
569 | { | |
570 | const char *p; | |
571 | ||
572 | printf(" <%s>", element); | |
573 | for (p = content; *p; ) { | |
574 | struct uuid uuid; | |
575 | ||
576 | if (uuid_from_string_prefix(&uuid, p)) { | |
577 | printf("<a href=\"#%.*s\">%.*s</a>", UUID_LEN, p, 8, p); | |
578 | p += UUID_LEN; | |
579 | } else { | |
580 | table_escape_html_text__(p, 1); | |
581 | p++; | |
582 | } | |
583 | } | |
d0632593 BP |
584 | printf("</%s>\n", element); |
585 | } | |
586 | ||
587 | static void | |
588 | table_print_html__(const struct table *table) | |
589 | { | |
590 | size_t x, y; | |
591 | ||
1f441586 | 592 | fputs("<table border=1>\n", stdout); |
d0632593 | 593 | |
25c269ef BP |
594 | if (table->caption) { |
595 | table_print_html_cell__("caption", table->caption); | |
596 | } | |
597 | ||
d0632593 BP |
598 | if (output_headings) { |
599 | fputs(" <tr>\n", stdout); | |
600 | for (x = 0; x < table->n_columns; x++) { | |
601 | const struct column *column = &table->columns[x]; | |
602 | table_print_html_cell__("th", column->heading); | |
603 | } | |
604 | fputs(" </tr>\n", stdout); | |
605 | } | |
606 | ||
607 | for (y = 0; y < table->n_rows; y++) { | |
608 | fputs(" <tr>\n", stdout); | |
609 | for (x = 0; x < table->n_columns; x++) { | |
772387d5 | 610 | const char *content = cell_to_text(table_cell__(table, y, x)); |
1f441586 BP |
611 | |
612 | if (!strcmp(table->columns[x].heading, "_uuid")) { | |
613 | fputs(" <td><a name=\"", stdout); | |
614 | table_escape_html_text__(content, strlen(content)); | |
615 | fputs("\">", stdout); | |
616 | table_escape_html_text__(content, 8); | |
617 | fputs("</a></td>\n", stdout); | |
618 | } else { | |
619 | table_print_html_cell__("td", content); | |
620 | } | |
d0632593 BP |
621 | } |
622 | fputs(" </tr>\n", stdout); | |
623 | } | |
624 | ||
625 | fputs("</table>\n", stdout); | |
626 | } | |
627 | ||
628 | static void | |
629 | table_print_csv_cell__(const char *content) | |
630 | { | |
631 | const char *p; | |
632 | ||
633 | if (!strpbrk(content, "\n\",")) { | |
634 | fputs(content, stdout); | |
635 | } else { | |
636 | putchar('"'); | |
637 | for (p = content; *p != '\0'; p++) { | |
638 | switch (*p) { | |
639 | case '"': | |
640 | fputs("\"\"", stdout); | |
641 | break; | |
642 | default: | |
643 | putchar(*p); | |
644 | break; | |
645 | } | |
646 | } | |
647 | putchar('"'); | |
648 | } | |
649 | } | |
650 | ||
651 | static void | |
652 | table_print_csv__(const struct table *table) | |
653 | { | |
e5125481 | 654 | static int n = 0; |
d0632593 BP |
655 | size_t x, y; |
656 | ||
e5125481 BP |
657 | if (n++ > 0) { |
658 | putchar('\n'); | |
659 | } | |
660 | ||
25c269ef BP |
661 | if (table->caption) { |
662 | puts(table->caption); | |
663 | } | |
664 | ||
d0632593 BP |
665 | if (output_headings) { |
666 | for (x = 0; x < table->n_columns; x++) { | |
667 | const struct column *column = &table->columns[x]; | |
668 | if (x) { | |
669 | putchar(','); | |
670 | } | |
671 | table_print_csv_cell__(column->heading); | |
672 | } | |
673 | putchar('\n'); | |
674 | } | |
675 | ||
676 | for (y = 0; y < table->n_rows; y++) { | |
677 | for (x = 0; x < table->n_columns; x++) { | |
678 | if (x) { | |
679 | putchar(','); | |
680 | } | |
772387d5 | 681 | table_print_csv_cell__(cell_to_text(table_cell__(table, y, x))); |
d0632593 BP |
682 | } |
683 | putchar('\n'); | |
684 | } | |
685 | } | |
686 | ||
ee890a61 BP |
687 | static void |
688 | table_print_json__(const struct table *table) | |
689 | { | |
690 | struct json *json, *headings, *data; | |
691 | size_t x, y; | |
692 | char *s; | |
693 | ||
694 | json = json_object_create(); | |
695 | if (table->caption) { | |
696 | json_object_put_string(json, "caption", table->caption); | |
697 | } | |
698 | ||
699 | headings = json_array_create_empty(); | |
700 | for (x = 0; x < table->n_columns; x++) { | |
701 | const struct column *column = &table->columns[x]; | |
702 | json_array_add(headings, json_string_create(column->heading)); | |
703 | } | |
704 | json_object_put(json, "headings", headings); | |
705 | ||
706 | data = json_array_create_empty(); | |
707 | for (y = 0; y < table->n_rows; y++) { | |
708 | struct json *row = json_array_create_empty(); | |
709 | for (x = 0; x < table->n_columns; x++) { | |
710 | const struct cell *cell = table_cell__(table, y, x); | |
711 | if (cell->text) { | |
712 | json_array_add(row, json_string_create(cell->text)); | |
713 | } else { | |
714 | json_array_add(row, json_clone(cell->json)); | |
715 | } | |
716 | } | |
717 | json_array_add(data, row); | |
718 | } | |
719 | json_object_put(json, "data", data); | |
720 | ||
721 | s = json_to_string(json, json_flags); | |
722 | json_destroy(json); | |
723 | puts(s); | |
724 | free(s); | |
725 | } | |
726 | ||
d0632593 BP |
727 | static void |
728 | table_print(const struct table *table) | |
729 | { | |
730 | switch (output_format) { | |
731 | case FMT_TABLE: | |
732 | table_print_table__(table); | |
733 | break; | |
734 | ||
735 | case FMT_HTML: | |
736 | table_print_html__(table); | |
737 | break; | |
738 | ||
739 | case FMT_CSV: | |
740 | table_print_csv__(table); | |
741 | break; | |
ee890a61 BP |
742 | |
743 | case FMT_JSON: | |
744 | table_print_json__(table); | |
745 | break; | |
d0632593 BP |
746 | } |
747 | } | |
748 | \f | |
9cb53f26 | 749 | static void |
c69ee87c | 750 | do_list_dbs(int argc OVS_UNUSED, char *argv[]) |
9cb53f26 BP |
751 | { |
752 | struct jsonrpc_msg *request, *reply; | |
753 | struct jsonrpc *rpc; | |
754 | int error; | |
755 | size_t i; | |
756 | ||
757 | rpc = open_jsonrpc(argv[1]); | |
758 | request = jsonrpc_create_request("list_dbs", json_array_create_empty(), | |
759 | NULL); | |
760 | error = jsonrpc_transact_block(rpc, request, &reply); | |
761 | if (error) { | |
762 | ovs_fatal(error, "transaction failed"); | |
763 | } | |
764 | ||
765 | if (reply->result->type != JSON_ARRAY) { | |
766 | ovs_fatal(0, "list_dbs response is not array"); | |
767 | } | |
768 | ||
769 | for (i = 0; i < reply->result->u.array.n; i++) { | |
770 | const struct json *name = reply->result->u.array.elems[i]; | |
771 | ||
772 | if (name->type != JSON_STRING) { | |
773 | ovs_fatal(0, "list_dbs response %zu is not string", i); | |
774 | } | |
775 | puts(name->u.string); | |
776 | } | |
777 | jsonrpc_msg_destroy(reply); | |
778 | } | |
779 | ||
d0632593 | 780 | static void |
c69ee87c | 781 | do_get_schema(int argc OVS_UNUSED, char *argv[]) |
d0632593 | 782 | { |
9cb53f26 | 783 | struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]); |
d0632593 BP |
784 | print_and_free_json(ovsdb_schema_to_json(schema)); |
785 | ovsdb_schema_destroy(schema); | |
786 | } | |
787 | ||
8159b984 BP |
788 | static void |
789 | do_get_schema_version(int argc OVS_UNUSED, char *argv[]) | |
790 | { | |
791 | struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]); | |
792 | puts(schema->version); | |
793 | ovsdb_schema_destroy(schema); | |
794 | } | |
795 | ||
d0632593 | 796 | static void |
c69ee87c | 797 | do_list_tables(int argc OVS_UNUSED, char *argv[]) |
d0632593 BP |
798 | { |
799 | struct ovsdb_schema *schema; | |
800 | struct shash_node *node; | |
801 | struct table t; | |
802 | ||
9cb53f26 | 803 | schema = fetch_schema(argv[1], argv[2]); |
d0632593 BP |
804 | table_init(&t); |
805 | table_add_column(&t, "Table"); | |
d0632593 BP |
806 | SHASH_FOR_EACH (node, &schema->tables) { |
807 | struct ovsdb_table_schema *ts = node->data; | |
808 | ||
809 | table_add_row(&t); | |
772387d5 | 810 | table_add_cell(&t)->text = xstrdup(ts->name); |
d0632593 BP |
811 | } |
812 | ovsdb_schema_destroy(schema); | |
813 | table_print(&t); | |
814 | } | |
815 | ||
816 | static void | |
c69ee87c | 817 | do_list_columns(int argc OVS_UNUSED, char *argv[]) |
d0632593 | 818 | { |
9cb53f26 | 819 | const char *table_name = argv[3]; |
d0632593 BP |
820 | struct ovsdb_schema *schema; |
821 | struct shash_node *table_node; | |
822 | struct table t; | |
823 | ||
9cb53f26 | 824 | schema = fetch_schema(argv[1], argv[2]); |
d0632593 BP |
825 | table_init(&t); |
826 | if (!table_name) { | |
827 | table_add_column(&t, "Table"); | |
828 | } | |
829 | table_add_column(&t, "Column"); | |
830 | table_add_column(&t, "Type"); | |
d0632593 BP |
831 | SHASH_FOR_EACH (table_node, &schema->tables) { |
832 | struct ovsdb_table_schema *ts = table_node->data; | |
833 | ||
834 | if (!table_name || !strcmp(table_name, ts->name)) { | |
835 | struct shash_node *column_node; | |
836 | ||
837 | SHASH_FOR_EACH (column_node, &ts->columns) { | |
bd76d25d | 838 | const struct ovsdb_column *column = column_node->data; |
d0632593 BP |
839 | |
840 | table_add_row(&t); | |
841 | if (!table_name) { | |
772387d5 | 842 | table_add_cell(&t)->text = xstrdup(ts->name); |
d0632593 | 843 | } |
772387d5 BP |
844 | table_add_cell(&t)->text = xstrdup(column->name); |
845 | table_add_cell(&t)->json = ovsdb_type_to_json(&column->type); | |
d0632593 BP |
846 | } |
847 | } | |
848 | } | |
849 | ovsdb_schema_destroy(schema); | |
850 | table_print(&t); | |
851 | } | |
852 | ||
6d65eee8 | 853 | static void |
c69ee87c | 854 | do_transact(int argc OVS_UNUSED, char *argv[]) |
6d65eee8 BP |
855 | { |
856 | struct jsonrpc_msg *request, *reply; | |
857 | struct json *transaction; | |
858 | struct jsonrpc *rpc; | |
859 | int error; | |
860 | ||
861 | transaction = parse_json(argv[2]); | |
862 | ||
863 | rpc = open_jsonrpc(argv[1]); | |
20bed8be | 864 | request = jsonrpc_create_request("transact", transaction, NULL); |
6d65eee8 BP |
865 | error = jsonrpc_transact_block(rpc, request, &reply); |
866 | if (error) { | |
867 | ovs_fatal(error, "transaction failed"); | |
868 | } | |
869 | if (reply->error) { | |
870 | ovs_fatal(error, "transaction returned error: %s", | |
b87fde85 | 871 | json_to_string(reply->error, json_flags)); |
6d65eee8 BP |
872 | } |
873 | print_json(reply->result); | |
874 | putchar('\n'); | |
875 | jsonrpc_msg_destroy(reply); | |
876 | jsonrpc_close(rpc); | |
877 | } | |
878 | ||
a8425c53 BP |
879 | static void |
880 | monitor_print_row(struct json *row, const char *type, const char *uuid, | |
881 | const struct ovsdb_column_set *columns, struct table *t) | |
882 | { | |
883 | size_t i; | |
884 | ||
885 | if (!row) { | |
886 | ovs_error(0, "missing %s row", type); | |
887 | return; | |
888 | } else if (row->type != JSON_OBJECT) { | |
889 | ovs_error(0, "<row> is not object"); | |
890 | return; | |
891 | } | |
892 | ||
893 | table_add_row(t); | |
772387d5 BP |
894 | table_add_cell(t)->text = xstrdup(uuid); |
895 | table_add_cell(t)->text = xstrdup(type); | |
a8425c53 BP |
896 | for (i = 0; i < columns->n_columns; i++) { |
897 | const struct ovsdb_column *column = columns->columns[i]; | |
898 | struct json *value = shash_find_data(json_object(row), column->name); | |
772387d5 | 899 | struct cell *cell = table_add_cell(t); |
a8425c53 | 900 | if (value) { |
772387d5 BP |
901 | cell->json = json_clone(value); |
902 | cell->type = &column->type; | |
a8425c53 BP |
903 | } |
904 | } | |
905 | } | |
906 | ||
907 | static void | |
908 | monitor_print(struct json *table_updates, | |
909 | const struct ovsdb_table_schema *table, | |
910 | const struct ovsdb_column_set *columns, bool initial) | |
911 | { | |
912 | struct json *table_update; | |
913 | struct shash_node *node; | |
914 | struct table t; | |
915 | size_t i; | |
916 | ||
917 | table_init(&t); | |
918 | ||
919 | if (table_updates->type != JSON_OBJECT) { | |
920 | ovs_error(0, "<table-updates> is not object"); | |
921 | return; | |
922 | } | |
923 | table_update = shash_find_data(json_object(table_updates), table->name); | |
924 | if (!table_update) { | |
925 | return; | |
926 | } | |
927 | if (table_update->type != JSON_OBJECT) { | |
928 | ovs_error(0, "<table-update> is not object"); | |
929 | return; | |
930 | } | |
931 | ||
932 | table_add_column(&t, "row"); | |
933 | table_add_column(&t, "action"); | |
934 | for (i = 0; i < columns->n_columns; i++) { | |
935 | table_add_column(&t, "%s", columns->columns[i]->name); | |
936 | } | |
937 | SHASH_FOR_EACH (node, json_object(table_update)) { | |
938 | struct json *row_update = node->data; | |
939 | struct json *old, *new; | |
940 | ||
941 | if (row_update->type != JSON_OBJECT) { | |
942 | ovs_error(0, "<row-update> is not object"); | |
943 | continue; | |
944 | } | |
945 | old = shash_find_data(json_object(row_update), "old"); | |
946 | new = shash_find_data(json_object(row_update), "new"); | |
947 | if (initial) { | |
948 | monitor_print_row(new, "initial", node->name, columns, &t); | |
949 | } else if (!old) { | |
950 | monitor_print_row(new, "insert", node->name, columns, &t); | |
951 | } else if (!new) { | |
952 | monitor_print_row(old, "delete", node->name, columns, &t); | |
953 | } else { | |
954 | monitor_print_row(old, "old", node->name, columns, &t); | |
955 | monitor_print_row(new, "new", "", columns, &t); | |
956 | } | |
957 | } | |
958 | table_print(&t); | |
959 | table_destroy(&t); | |
960 | } | |
961 | ||
962 | static void | |
20aa445d BP |
963 | add_column(const char *server, const struct ovsdb_column *column, |
964 | struct ovsdb_column_set *columns, struct json *columns_json) | |
a8425c53 | 965 | { |
20aa445d BP |
966 | if (ovsdb_column_set_contains(columns, column->index)) { |
967 | ovs_fatal(0, "%s: column \"%s\" mentioned multiple times", | |
968 | server, column->name); | |
a8425c53 | 969 | } |
20aa445d BP |
970 | ovsdb_column_set_add(columns, column); |
971 | json_array_add(columns_json, json_string_create(column->name)); | |
972 | } | |
a8425c53 | 973 | |
20aa445d BP |
974 | static struct json * |
975 | parse_monitor_columns(char *arg, const char *server, const char *database, | |
976 | const struct ovsdb_table_schema *table, | |
977 | struct ovsdb_column_set *columns) | |
978 | { | |
979 | bool initial, insert, delete, modify; | |
980 | struct json *mr, *columns_json; | |
981 | char *save_ptr = NULL; | |
982 | char *token; | |
983 | ||
984 | mr = json_object_create(); | |
985 | columns_json = json_array_create_empty(); | |
986 | json_object_put(mr, "columns", columns_json); | |
987 | ||
988 | initial = insert = delete = modify = true; | |
989 | for (token = strtok_r(arg, ",", &save_ptr); token != NULL; | |
990 | token = strtok_r(NULL, ",", &save_ptr)) { | |
991 | if (!strcmp(token, "!initial")) { | |
992 | initial = false; | |
993 | } else if (!strcmp(token, "!insert")) { | |
994 | insert = false; | |
995 | } else if (!strcmp(token, "!delete")) { | |
996 | delete = false; | |
997 | } else if (!strcmp(token, "!modify")) { | |
998 | modify = false; | |
999 | } else { | |
a8425c53 | 1000 | const struct ovsdb_column *column; |
20aa445d | 1001 | |
a8425c53 BP |
1002 | column = ovsdb_table_schema_get_column(table, token); |
1003 | if (!column) { | |
9cb53f26 BP |
1004 | ovs_fatal(0, "%s: table \"%s\" in %s does not have a " |
1005 | "column named \"%s\"", | |
20aa445d | 1006 | server, table->name, database, token); |
a8425c53 | 1007 | } |
20aa445d | 1008 | add_column(server, column, columns, columns_json); |
a8425c53 | 1009 | } |
20aa445d BP |
1010 | } |
1011 | ||
1012 | if (columns_json->u.array.n == 0) { | |
a1ae5dc8 BP |
1013 | const struct shash_node **nodes; |
1014 | size_t i, n; | |
1015 | ||
1016 | n = shash_count(&table->columns); | |
1017 | nodes = shash_sort(&table->columns); | |
1018 | for (i = 0; i < n; i++) { | |
1019 | const struct ovsdb_column *column = nodes[i]->data; | |
1020 | if (column->index != OVSDB_COL_UUID | |
1021 | && column->index != OVSDB_COL_VERSION) { | |
20aa445d | 1022 | add_column(server, column, columns, columns_json); |
a8425c53 BP |
1023 | } |
1024 | } | |
a1ae5dc8 BP |
1025 | free(nodes); |
1026 | ||
20aa445d BP |
1027 | add_column(server, ovsdb_table_schema_get_column(table,"_version"), |
1028 | columns, columns_json); | |
a8425c53 BP |
1029 | } |
1030 | ||
20aa445d BP |
1031 | if (!initial || !insert || !delete || !modify) { |
1032 | struct json *select = json_object_create(); | |
876ba6de BP |
1033 | json_object_put(select, "initial", json_boolean_create(initial)); |
1034 | json_object_put(select, "insert", json_boolean_create(insert)); | |
1035 | json_object_put(select, "delete", json_boolean_create(delete)); | |
1036 | json_object_put(select, "modify", json_boolean_create(modify)); | |
20aa445d BP |
1037 | json_object_put(mr, "select", select); |
1038 | } | |
1039 | ||
1040 | return mr; | |
1041 | } | |
1042 | ||
1043 | static void | |
1044 | do_monitor(int argc, char *argv[]) | |
1045 | { | |
1046 | const char *server = argv[1]; | |
1047 | const char *database = argv[2]; | |
1048 | const char *table_name = argv[3]; | |
1049 | struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; | |
1050 | struct ovsdb_table_schema *table; | |
1051 | struct ovsdb_schema *schema; | |
1052 | struct jsonrpc_msg *request; | |
1053 | struct jsonrpc *rpc; | |
1054 | struct json *monitor, *monitor_request_array, | |
1055 | *monitor_requests, *request_id; | |
1056 | ||
1057 | rpc = open_jsonrpc(server); | |
1058 | ||
1059 | schema = fetch_schema_from_rpc(rpc, database); | |
1060 | table = shash_find_data(&schema->tables, table_name); | |
1061 | if (!table) { | |
1062 | ovs_fatal(0, "%s: %s does not have a table named \"%s\"", | |
1063 | server, database, table_name); | |
a8425c53 BP |
1064 | } |
1065 | ||
20aa445d BP |
1066 | monitor_request_array = json_array_create_empty(); |
1067 | if (argc > 4) { | |
1068 | int i; | |
1069 | ||
1070 | for (i = 4; i < argc; i++) { | |
1071 | json_array_add( | |
1072 | monitor_request_array, | |
1073 | parse_monitor_columns(argv[i], server, database, table, | |
1074 | &columns)); | |
1075 | } | |
1076 | } else { | |
1077 | /* Allocate a writable empty string since parse_monitor_columns() is | |
1078 | * going to strtok() it and that's risky with literal "". */ | |
1079 | char empty[] = ""; | |
1080 | json_array_add( | |
1081 | monitor_request_array, | |
1082 | parse_monitor_columns(empty, server, database, table, &columns)); | |
a8425c53 BP |
1083 | } |
1084 | ||
1085 | monitor_requests = json_object_create(); | |
20aa445d | 1086 | json_object_put(monitor_requests, table_name, monitor_request_array); |
a8425c53 | 1087 | |
9cb53f26 BP |
1088 | monitor = json_array_create_3(json_string_create(database), |
1089 | json_null_create(), monitor_requests); | |
20bed8be | 1090 | request = jsonrpc_create_request("monitor", monitor, NULL); |
a8425c53 BP |
1091 | request_id = json_clone(request->id); |
1092 | jsonrpc_send(rpc, request); | |
1093 | for (;;) { | |
1094 | struct jsonrpc_msg *msg; | |
1095 | int error; | |
1096 | ||
1097 | error = jsonrpc_recv_block(rpc, &msg); | |
1098 | if (error) { | |
ce7ebcdf | 1099 | ovsdb_schema_destroy(schema); |
9cb53f26 | 1100 | ovs_fatal(error, "%s: receive failed", server); |
a8425c53 BP |
1101 | } |
1102 | ||
1103 | if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) { | |
1104 | jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params), | |
1105 | msg->id)); | |
1106 | } else if (msg->type == JSONRPC_REPLY | |
1107 | && json_equal(msg->id, request_id)) { | |
1108 | monitor_print(msg->result, table, &columns, true); | |
3f262d7d | 1109 | fflush(stdout); |
eb8d3ed6 BP |
1110 | if (get_detach()) { |
1111 | /* daemonize() closes the standard file descriptors. We output | |
1112 | * to stdout, so we need to save and restore STDOUT_FILENO. */ | |
1113 | int fd = dup(STDOUT_FILENO); | |
1114 | daemonize(); | |
1115 | dup2(fd, STDOUT_FILENO); | |
1116 | close(fd); | |
1117 | } | |
a8425c53 BP |
1118 | } else if (msg->type == JSONRPC_NOTIFY |
1119 | && !strcmp(msg->method, "update")) { | |
1120 | struct json *params = msg->params; | |
1121 | if (params->type == JSON_ARRAY | |
1122 | && params->u.array.n == 2 | |
1123 | && params->u.array.elems[0]->type == JSON_NULL) { | |
1124 | monitor_print(params->u.array.elems[1], | |
1125 | table, &columns, false); | |
3f262d7d | 1126 | fflush(stdout); |
a8425c53 BP |
1127 | } |
1128 | } | |
ce7ebcdf | 1129 | jsonrpc_msg_destroy(msg); |
a8425c53 BP |
1130 | } |
1131 | } | |
1132 | ||
25c269ef BP |
1133 | struct dump_table_aux { |
1134 | struct ovsdb_datum **data; | |
1135 | const struct ovsdb_column **columns; | |
1136 | size_t n_columns; | |
1137 | }; | |
1138 | ||
1139 | static int | |
1140 | compare_data(size_t a_y, size_t b_y, size_t x, | |
1141 | const struct dump_table_aux *aux) | |
1142 | { | |
1143 | return ovsdb_datum_compare_3way(&aux->data[a_y][x], | |
1144 | &aux->data[b_y][x], | |
1145 | &aux->columns[x]->type); | |
1146 | } | |
1147 | ||
1148 | static int | |
1149 | compare_rows(size_t a_y, size_t b_y, void *aux_) | |
1150 | { | |
1151 | struct dump_table_aux *aux = aux_; | |
1152 | size_t x; | |
1153 | ||
1154 | /* Skip UUID columns on the first pass, since their values tend to be | |
1155 | * random and make our results less reproducible. */ | |
1156 | for (x = 0; x < aux->n_columns; x++) { | |
1157 | if (aux->columns[x]->type.key.type != OVSDB_TYPE_UUID) { | |
1158 | int cmp = compare_data(a_y, b_y, x, aux); | |
1159 | if (cmp) { | |
1160 | return cmp; | |
1161 | } | |
1162 | } | |
1163 | } | |
1164 | ||
1165 | /* Use UUID columns as tie-breakers. */ | |
1166 | for (x = 0; x < aux->n_columns; x++) { | |
1167 | if (aux->columns[x]->type.key.type == OVSDB_TYPE_UUID) { | |
1168 | int cmp = compare_data(a_y, b_y, x, aux); | |
1169 | if (cmp) { | |
1170 | return cmp; | |
1171 | } | |
1172 | } | |
1173 | } | |
1174 | ||
1175 | return 0; | |
1176 | } | |
1177 | ||
1178 | static void | |
1179 | swap_rows(size_t a_y, size_t b_y, void *aux_) | |
1180 | { | |
1181 | struct dump_table_aux *aux = aux_; | |
1182 | struct ovsdb_datum *tmp = aux->data[a_y]; | |
1183 | aux->data[a_y] = aux->data[b_y]; | |
1184 | aux->data[b_y] = tmp; | |
1185 | } | |
1186 | ||
25c269ef BP |
1187 | static int |
1188 | compare_columns(const void *a_, const void *b_) | |
1189 | { | |
1190 | const struct ovsdb_column *const *ap = a_; | |
1191 | const struct ovsdb_column *const *bp = b_; | |
1192 | const struct ovsdb_column *a = *ap; | |
1193 | const struct ovsdb_column *b = *bp; | |
1194 | ||
1195 | return strcmp(a->name, b->name); | |
1196 | } | |
1197 | ||
1198 | static void | |
1199 | dump_table(const struct ovsdb_table_schema *ts, struct json_array *rows) | |
1200 | { | |
1201 | const struct ovsdb_column **columns; | |
1202 | size_t n_columns; | |
1203 | ||
1204 | struct ovsdb_datum **data; | |
1205 | ||
1206 | struct dump_table_aux aux; | |
1207 | struct shash_node *node; | |
1208 | struct table t; | |
1209 | size_t x, y; | |
1210 | ||
1211 | /* Sort columns by name, for reproducibility. */ | |
1212 | columns = xmalloc(shash_count(&ts->columns) * sizeof *columns); | |
1213 | n_columns = 0; | |
1214 | SHASH_FOR_EACH (node, &ts->columns) { | |
1215 | struct ovsdb_column *column = node->data; | |
1216 | if (strcmp(column->name, "_version")) { | |
1217 | columns[n_columns++] = column; | |
1218 | } | |
1219 | } | |
1220 | qsort(columns, n_columns, sizeof *columns, compare_columns); | |
1221 | ||
1222 | /* Extract data from table. */ | |
1223 | data = xmalloc(rows->n * sizeof *data); | |
1224 | for (y = 0; y < rows->n; y++) { | |
1225 | struct shash *row; | |
1226 | ||
1227 | if (rows->elems[y]->type != JSON_OBJECT) { | |
1228 | ovs_fatal(0, "row %zu in table %s response is not a JSON object: " | |
1229 | "%s", y, ts->name, json_to_string(rows->elems[y], 0)); | |
1230 | } | |
1231 | row = json_object(rows->elems[y]); | |
1232 | ||
1233 | data[y] = xmalloc(n_columns * sizeof **data); | |
1234 | for (x = 0; x < n_columns; x++) { | |
1235 | const struct json *json = shash_find_data(row, columns[x]->name); | |
1236 | if (!json) { | |
1237 | ovs_fatal(0, "row %zu in table %s response lacks %s column", | |
1238 | y, ts->name, columns[x]->name); | |
1239 | } | |
1240 | ||
1241 | check_ovsdb_error(ovsdb_datum_from_json(&data[y][x], | |
1242 | &columns[x]->type, | |
1243 | json, NULL)); | |
1244 | } | |
1245 | } | |
1246 | ||
1247 | /* Sort rows by column values, for reproducibility. */ | |
1248 | aux.data = data; | |
1249 | aux.columns = columns; | |
1250 | aux.n_columns = n_columns; | |
1251 | sort(rows->n, compare_rows, swap_rows, &aux); | |
1252 | ||
1253 | /* Add column headings. */ | |
1254 | table_init(&t); | |
1255 | table_set_caption(&t, xasprintf("%s table", ts->name)); | |
1256 | for (x = 0; x < n_columns; x++) { | |
1257 | table_add_column(&t, "%s", columns[x]->name); | |
1258 | } | |
1259 | ||
1260 | /* Print rows. */ | |
1261 | for (y = 0; y < rows->n; y++) { | |
1262 | table_add_row(&t); | |
1263 | for (x = 0; x < n_columns; x++) { | |
772387d5 BP |
1264 | struct cell *cell = table_add_cell(&t); |
1265 | cell->json = ovsdb_datum_to_json(&data[y][x], &columns[x]->type); | |
1266 | cell->type = &columns[x]->type; | |
25c269ef BP |
1267 | } |
1268 | } | |
1269 | table_print(&t); | |
1270 | table_destroy(&t); | |
1271 | } | |
1272 | ||
1273 | static void | |
1274 | do_dump(int argc OVS_UNUSED, char *argv[]) | |
1275 | { | |
1276 | const char *server = argv[1]; | |
1277 | const char *database = argv[2]; | |
1278 | ||
1279 | struct jsonrpc_msg *request, *reply; | |
1280 | struct ovsdb_schema *schema; | |
1281 | struct json *transaction; | |
1282 | struct jsonrpc *rpc; | |
1283 | int error; | |
1284 | ||
1285 | const struct shash_node **tables; | |
1286 | size_t n_tables; | |
1287 | ||
1288 | size_t i; | |
1289 | ||
1290 | rpc = open_jsonrpc(server); | |
1291 | ||
1292 | schema = fetch_schema_from_rpc(rpc, database); | |
1293 | tables = shash_sort(&schema->tables); | |
1294 | n_tables = shash_count(&schema->tables); | |
1295 | ||
1296 | /* Construct transaction to retrieve entire database. */ | |
1297 | transaction = json_array_create_1(json_string_create(database)); | |
1298 | for (i = 0; i < n_tables; i++) { | |
1299 | const struct ovsdb_table_schema *ts = tables[i]->data; | |
1300 | struct json *op, *columns; | |
1301 | struct shash_node *node; | |
1302 | ||
1303 | columns = json_array_create_empty(); | |
1304 | SHASH_FOR_EACH (node, &ts->columns) { | |
1305 | const struct ovsdb_column *column = node->data; | |
1306 | ||
1307 | if (strcmp(column->name, "_version")) { | |
1308 | json_array_add(columns, json_string_create(column->name)); | |
1309 | } | |
1310 | } | |
1311 | ||
1312 | op = json_object_create(); | |
1313 | json_object_put_string(op, "op", "select"); | |
1314 | json_object_put_string(op, "table", tables[i]->name); | |
1315 | json_object_put(op, "where", json_array_create_empty()); | |
1316 | json_object_put(op, "columns", columns); | |
1317 | json_array_add(transaction, op); | |
1318 | } | |
1319 | ||
1320 | /* Send request, get reply. */ | |
1321 | request = jsonrpc_create_request("transact", transaction, NULL); | |
1322 | error = jsonrpc_transact_block(rpc, request, &reply); | |
1323 | if (error) { | |
1324 | ovs_fatal(error, "transaction failed"); | |
1325 | } | |
1326 | ||
1327 | /* Print database contents. */ | |
1328 | if (reply->result->type != JSON_ARRAY | |
1329 | || reply->result->u.array.n != n_tables) { | |
1330 | ovs_fatal(0, "reply is not array of %zu elements: %s", | |
1331 | n_tables, json_to_string(reply->result, 0)); | |
1332 | } | |
1333 | for (i = 0; i < n_tables; i++) { | |
1334 | const struct ovsdb_table_schema *ts = tables[i]->data; | |
1335 | const struct json *op_result = reply->result->u.array.elems[i]; | |
1336 | struct json *rows; | |
1337 | ||
1338 | if (op_result->type != JSON_OBJECT | |
1339 | || !(rows = shash_find_data(json_object(op_result), "rows")) | |
1340 | || rows->type != JSON_ARRAY) { | |
1341 | ovs_fatal(0, "%s table reply is not an object with a \"rows\" " | |
1342 | "member array: %s", | |
1343 | ts->name, json_to_string(op_result, 0)); | |
1344 | } | |
1345 | ||
1346 | dump_table(ts, &rows->u.array); | |
1347 | } | |
1348 | } | |
1349 | ||
d0632593 | 1350 | static void |
c69ee87c | 1351 | do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) |
d0632593 BP |
1352 | { |
1353 | usage(); | |
1354 | } | |
1355 | ||
1356 | static const struct command all_commands[] = { | |
9cb53f26 BP |
1357 | { "list-dbs", 1, 1, do_list_dbs }, |
1358 | { "get-schema", 2, 2, do_get_schema }, | |
8159b984 | 1359 | { "get-schema-version", 2, 2, do_get_schema_version }, |
9cb53f26 BP |
1360 | { "list-tables", 2, 2, do_list_tables }, |
1361 | { "list-columns", 2, 3, do_list_columns }, | |
6d65eee8 | 1362 | { "transact", 2, 2, do_transact }, |
20aa445d | 1363 | { "monitor", 3, INT_MAX, do_monitor }, |
25c269ef | 1364 | { "dump", 2, 2, do_dump }, |
d0632593 BP |
1365 | { "help", 0, INT_MAX, do_help }, |
1366 | { NULL, 0, 0, NULL }, | |
1367 | }; |