pg: A Tunnel mode variant of a pg interface
[vpp.git] / src / vnet / pg / cli.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 /*
16  * pg_cli.c: packet generator cli
17  *
18  * Copyright (c) 2008 Eliot Dresselhaus
19  *
20  * Permission is hereby granted, free of charge, to any person obtaining
21  * a copy of this software and associated documentation files (the
22  * "Software"), to deal in the Software without restriction, including
23  * without limitation the rights to use, copy, modify, merge, publish,
24  * distribute, sublicense, and/or sell copies of the Software, and to
25  * permit persons to whom the Software is furnished to do so, subject to
26  * the following conditions:
27  *
28  * The above copyright notice and this permission notice shall be
29  * included in all copies or substantial portions of the Software.
30  *
31  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32  *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33  *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34  *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35  *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36  *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37  *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38  */
39
40 #include <sys/stat.h>
41
42 #include <vnet/vnet.h>
43 #include <vnet/pg/pg.h>
44
45 #include <strings.h>
46 #include <vppinfra/pcap.h>
47
48
49 /* Root of all packet generator cli commands. */
50 /* *INDENT-OFF* */
51 VLIB_CLI_COMMAND (vlib_cli_pg_command, static) = {
52   .path = "packet-generator",
53   .short_help = "Packet generator commands",
54 };
55 /* *INDENT-ON* */
56
57 void
58 pg_enable_disable (u32 stream_index, int is_enable)
59 {
60   pg_main_t *pg = &pg_main;
61   pg_stream_t *s;
62
63   if (stream_index == ~0)
64     {
65       /* No stream specified: enable/disable all streams. */
66       /* *INDENT-OFF* */
67         pool_foreach (s, pg->streams)  {
68             pg_stream_enable_disable (pg, s, is_enable);
69         }
70         /* *INDENT-ON* */
71     }
72   else
73     {
74       /* enable/disable specified stream. */
75       s = pool_elt_at_index (pg->streams, stream_index);
76       pg_stream_enable_disable (pg, s, is_enable);
77     }
78 }
79
80 clib_error_t *
81 pg_capture (pg_capture_args_t * a)
82 {
83   pg_main_t *pg = &pg_main;
84   pg_interface_t *pi;
85
86   if (a->is_enabled == 1)
87     {
88       struct stat sb;
89       if (stat (a->pcap_file_name, &sb) != -1)
90         return clib_error_return (0, "pcap file '%s' already exists.",
91                                   a->pcap_file_name);
92     }
93
94   pi = pool_elt_at_index (pg->interfaces, a->dev_instance);
95   vec_free (pi->pcap_file_name);
96   if ((pi->pcap_main.flags & PCAP_MAIN_INIT_DONE))
97     pcap_close (&pi->pcap_main);
98   clib_memset (&pi->pcap_main, 0, sizeof (pi->pcap_main));
99   pi->pcap_main.file_descriptor = -1;
100
101   if (a->is_enabled == 0)
102     return 0;
103
104   pi->pcap_file_name = a->pcap_file_name;
105   pi->pcap_main.file_name = (char *) pi->pcap_file_name;
106   pi->pcap_main.n_packets_to_capture = a->count;
107   pi->pcap_main.packet_type = PCAP_PACKET_TYPE_ethernet;
108
109   return 0;
110 }
111
112 static clib_error_t *
113 enable_disable_stream (vlib_main_t * vm,
114                        unformat_input_t * input, vlib_cli_command_t * cmd)
115 {
116   unformat_input_t _line_input, *line_input = &_line_input;
117   pg_main_t *pg = &pg_main;
118   int is_enable = cmd->function_arg != 0;
119   u32 stream_index = ~0;
120
121   if (!unformat_user (input, unformat_line_input, line_input))
122     goto doit;
123
124   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
125     {
126       if (unformat (line_input, "%U", unformat_hash_vec_string,
127                     pg->stream_index_by_name, &stream_index))
128         ;
129       else
130         return clib_error_create ("unknown input `%U'",
131                                   format_unformat_error, line_input);
132     }
133   unformat_free (line_input);
134
135 doit:
136   pg_enable_disable (stream_index, is_enable);
137
138   return 0;
139 }
140
141 /* *INDENT-OFF* */
142 VLIB_CLI_COMMAND (enable_streams_cli, static) = {
143   .path = "packet-generator enable-stream",
144   .short_help = "Enable packet generator streams",
145   .function = enable_disable_stream,
146   .function_arg = 1,            /* is_enable */
147 };
148 /* *INDENT-ON* */
149
150 /* *INDENT-OFF* */
151 VLIB_CLI_COMMAND (disable_streams_cli, static) = {
152   .path = "packet-generator disable-stream",
153   .short_help = "Disable packet generator streams",
154   .function = enable_disable_stream,
155   .function_arg = 0,            /* is_enable */
156 };
157 /* *INDENT-ON* */
158
159 static u8 *
160 format_pg_edit_group (u8 * s, va_list * va)
161 {
162   pg_edit_group_t *g = va_arg (*va, pg_edit_group_t *);
163
164   s =
165     format (s, "hdr-size %d, offset %d, ", g->n_packet_bytes,
166             g->start_byte_offset);
167   if (g->edit_function)
168     {
169       u8 *function_name;
170       u8 *junk_after_name;
171       function_name = format (0, "%U%c", format_clib_elf_symbol_with_address,
172                               g->edit_function, 0);
173       junk_after_name = function_name;
174       while (*junk_after_name && *junk_after_name != ' ')
175         junk_after_name++;
176       *junk_after_name = 0;
177       s = format (s, "edit-function %s, ", function_name);
178       vec_free (function_name);
179     }
180
181   return s;
182 }
183
184 static u8 *
185 format_pg_stream (u8 * s, va_list * va)
186 {
187   pg_stream_t *t = va_arg (*va, pg_stream_t *);
188   int verbose = va_arg (*va, int);
189
190   if (!t)
191     return format (s, "%-16s%=12s%=16s%s",
192                    "Name", "Enabled", "Count", "Parameters");
193
194   s = format (s, "%-16v%=12s%=16Ld",
195               t->name,
196               pg_stream_is_enabled (t) ? "Yes" : "No",
197               t->n_packets_generated);
198
199   int indent = format_get_indent (s);
200
201   s = format (s, "limit %Ld, ", t->n_packets_limit);
202   s = format (s, "rate %.2e pps, ", t->rate_packets_per_second);
203   s = format (s, "size %d%c%d, ",
204               t->min_packet_bytes,
205               t->packet_size_edit_type == PG_EDIT_RANDOM ? '+' : '-',
206               t->max_packet_bytes);
207   s = format (s, "buffer-size %d, ", t->buffer_bytes);
208   s = format (s, "worker %d, ", t->worker_index);
209
210   if (verbose)
211     {
212       pg_edit_group_t *g;
213   /* *INDENT-OFF* */
214   vec_foreach (g, t->edit_groups)
215     {
216       s = format (s, "\n%U%U", format_white_space, indent, format_pg_edit_group, g);
217     }
218   /* *INDENT-ON* */
219     }
220
221   return s;
222 }
223
224 static clib_error_t *
225 show_streams (vlib_main_t * vm,
226               unformat_input_t * input, vlib_cli_command_t * cmd)
227 {
228   pg_main_t *pg = &pg_main;
229   pg_stream_t *s;
230   int verbose = 0;
231
232   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
233     {
234       if (unformat (input, "verbose"))
235         verbose = 1;
236       else
237         break;
238     }
239
240   if (pool_elts (pg->streams) == 0)
241     {
242       vlib_cli_output (vm, "no streams currently defined");
243       goto done;
244     }
245
246   vlib_cli_output (vm, "%U", format_pg_stream, 0, 0);
247   /* *INDENT-OFF* */
248   pool_foreach (s, pg->streams)  {
249       vlib_cli_output (vm, "%U", format_pg_stream, s, verbose);
250     }
251   /* *INDENT-ON* */
252
253 done:
254   return 0;
255 }
256
257 /* *INDENT-OFF* */
258 VLIB_CLI_COMMAND (show_streams_cli, static) = {
259   .path = "show packet-generator ",
260   .short_help = "show packet-generator [verbose]",
261   .function = show_streams,
262 };
263 /* *INDENT-ON* */
264
265 static clib_error_t *
266 pg_pcap_read (pg_stream_t * s, char *file_name)
267 {
268 #ifndef CLIB_UNIX
269   return clib_error_return (0, "no pcap support");
270 #else
271   pcap_main_t pm;
272   clib_error_t *error;
273   clib_memset (&pm, 0, sizeof (pm));
274   pm.file_name = file_name;
275   error = pcap_read (&pm);
276   s->replay_packet_templates = pm.packets_read;
277   s->replay_packet_timestamps = pm.timestamps;
278   s->min_packet_bytes = pm.min_packet_bytes;
279   s->max_packet_bytes = pm.max_packet_bytes;
280   s->buffer_bytes = pm.max_packet_bytes;
281
282   if (s->n_packets_limit == 0)
283     s->n_packets_limit = vec_len (pm.packets_read);
284
285   return error;
286 #endif /* CLIB_UNIX */
287 }
288
289 static uword
290 unformat_pg_stream_parameter (unformat_input_t * input, va_list * args)
291 {
292   pg_stream_t *s = va_arg (*args, pg_stream_t *);
293   f64 x;
294
295   if (unformat (input, "limit %f", &x))
296     s->n_packets_limit = x;
297
298   else if (unformat (input, "rate %f", &x))
299     s->rate_packets_per_second = x;
300
301   else if (unformat (input, "size %d-%d", &s->min_packet_bytes,
302                      &s->max_packet_bytes))
303     s->packet_size_edit_type = PG_EDIT_INCREMENT;
304
305   else if (unformat (input, "size %d+%d", &s->min_packet_bytes,
306                      &s->max_packet_bytes))
307     s->packet_size_edit_type = PG_EDIT_RANDOM;
308
309   else if (unformat (input, "buffer-size %d", &s->buffer_bytes))
310     ;
311
312   else
313     return 0;
314
315   return 1;
316 }
317
318 static clib_error_t *
319 validate_stream (pg_stream_t * s)
320 {
321   if (s->max_packet_bytes < s->min_packet_bytes)
322     return clib_error_create ("max-size < min-size");
323
324   u32 hdr_size = pg_edit_group_n_bytes (s, 0);
325   if (s->min_packet_bytes < hdr_size)
326     return clib_error_create ("min-size < total header size %d", hdr_size);
327   if (s->buffer_bytes == 0)
328     return clib_error_create ("buffer-size must be positive");
329
330   if (s->rate_packets_per_second < 0)
331     return clib_error_create ("negative rate");
332
333   return 0;
334 }
335
336 const char *
337 pg_interface_get_input_node (pg_interface_t *pi)
338 {
339   switch (pi->mode)
340     {
341     case PG_MODE_ETHERNET:
342       return ("ethernet-input");
343     case PG_MODE_IP4:
344       return ("ip4-input");
345     case PG_MODE_IP6:
346       return ("ip6-input");
347     }
348
349   ASSERT (0);
350   return ("ethernet-input");
351 }
352
353 static clib_error_t *
354 new_stream (vlib_main_t * vm,
355             unformat_input_t * input, vlib_cli_command_t * cmd)
356 {
357   clib_error_t *error = 0;
358   u8 *tmp = 0;
359   u32 maxframe, hw_if_index;
360   unformat_input_t sub_input = { 0 };
361   int sub_input_given = 0;
362   vnet_main_t *vnm = vnet_get_main ();
363   pg_main_t *pg = &pg_main;
364   pg_stream_t s = { 0 };
365   char *pcap_file_name;
366
367   s.sw_if_index[VLIB_RX] = s.sw_if_index[VLIB_TX] = ~0;
368   s.node_index = ~0;
369   s.max_packet_bytes = s.min_packet_bytes = 64;
370   s.buffer_bytes = vlib_buffer_get_default_data_size (vm);
371   s.if_id = ~0;
372   s.n_max_frame = VLIB_FRAME_SIZE;
373   pcap_file_name = 0;
374
375   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
376     {
377       if (unformat (input, "name %v", &tmp))
378         {
379           if (s.name)
380             vec_free (s.name);
381           s.name = tmp;
382         }
383
384       else if (unformat (input, "node %U",
385                          unformat_vnet_hw_interface, vnm, &hw_if_index))
386         {
387           vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
388
389           s.node_index = hi->output_node_index;
390           s.sw_if_index[VLIB_TX] = hi->sw_if_index;
391         }
392
393       else if (unformat (input, "source pg%u", &s.if_id))
394         ;
395
396       else if (unformat (input, "buffer-flags %U",
397                          unformat_vnet_buffer_flags, &s.buffer_flags))
398         ;
399       else if (unformat (input, "buffer-offload-flags %U",
400                          unformat_vnet_buffer_offload_flags, &s.buffer_oflags))
401         ;
402       else if (unformat (input, "node %U",
403                          unformat_vlib_node, vm, &s.node_index))
404         ;
405       else if (unformat (input, "maxframe %u", &maxframe))
406         s.n_max_frame = s.n_max_frame < maxframe ? s.n_max_frame : maxframe;
407       else if (unformat (input, "worker %u", &s.worker_index))
408         ;
409
410       else if (unformat (input, "interface %U",
411                          unformat_vnet_sw_interface, vnm,
412                          &s.sw_if_index[VLIB_RX]))
413         ;
414       else if (unformat (input, "tx-interface %U",
415                          unformat_vnet_sw_interface, vnm,
416                          &s.sw_if_index[VLIB_TX]))
417         ;
418
419       else if (unformat (input, "pcap %s", &pcap_file_name))
420         ;
421
422       else if (!sub_input_given
423                && unformat (input, "data %U", unformat_input, &sub_input))
424         sub_input_given++;
425
426       else if (unformat_user (input, unformat_pg_stream_parameter, &s))
427         ;
428
429       else
430         {
431           error = clib_error_create ("unknown input `%U'",
432                                      format_unformat_error, input);
433           goto done;
434         }
435     }
436
437   if (!sub_input_given && !pcap_file_name)
438     {
439       error = clib_error_create ("no packet data given");
440       goto done;
441     }
442
443   if (s.node_index == ~0)
444     {
445       if (pcap_file_name != 0)
446         {
447           vlib_node_t *n;
448
449           ASSERT (s.if_id != ~0);
450
451           if (s.if_id != ~0)
452             n = vlib_get_node_by_name (vm, (u8 *) pg_interface_get_input_node (
453                                              &pg->interfaces[s.if_id]));
454           else
455             n = vlib_get_node_by_name (vm, (u8 *) "ethernet-input");
456           s.node_index = n->index;
457         }
458       else
459         {
460           error = clib_error_create ("output interface or node not given");
461           goto done;
462         }
463     }
464
465   {
466     pg_node_t *n;
467
468     if (s.node_index < vec_len (pg->nodes))
469       n = pg->nodes + s.node_index;
470     else
471       n = 0;
472
473     if (s.worker_index >= vlib_num_workers ())
474       s.worker_index = 0;
475
476     if (pcap_file_name != 0)
477       {
478         error = pg_pcap_read (&s, pcap_file_name);
479         if (error)
480           goto done;
481         vec_free (pcap_file_name);
482       }
483
484     else if (n && n->unformat_edit
485              && unformat_user (&sub_input, n->unformat_edit, &s))
486       ;
487
488     else if (!unformat_user (&sub_input, unformat_pg_payload, &s))
489       {
490         error = clib_error_create
491           ("failed to parse packet data from `%U'",
492            format_unformat_error, &sub_input);
493         goto done;
494       }
495   }
496
497   error = validate_stream (&s);
498   if (error)
499     return error;
500
501   pg_stream_add (pg, &s);
502   return 0;
503
504 done:
505   pg_stream_free (&s);
506   unformat_free (&sub_input);
507   return error;
508 }
509
510 /* *INDENT-OFF* */
511 VLIB_CLI_COMMAND (new_stream_cli, static) = {
512   .path = "packet-generator new",
513   .function = new_stream,
514   .short_help = "Create packet generator stream",
515   .long_help =
516   "Create packet generator stream\n"
517   "\n"
518   "Arguments:\n"
519   "\n"
520   "name STRING          sets stream name\n"
521   "interface STRING     interface for stream output \n"
522   "node NODE-NAME       node for stream output\n"
523   "data STRING          specifies packet data\n"
524   "pcap FILENAME        read packet data from pcap file\n"
525   "rate PPS             rate to transfer packet data\n"
526   "maxframe NPKTS       maximum number of packets per frame\n",
527 };
528 /* *INDENT-ON* */
529
530 static clib_error_t *
531 del_stream (vlib_main_t * vm,
532             unformat_input_t * input, vlib_cli_command_t * cmd)
533 {
534   pg_main_t *pg = &pg_main;
535   u32 i;
536
537   if (!unformat (input, "%U",
538                  &unformat_hash_vec_string, pg->stream_index_by_name, &i))
539     return clib_error_create ("expected stream name `%U'",
540                               format_unformat_error, input);
541
542   pg_stream_del (pg, i);
543   return 0;
544 }
545
546 /* *INDENT-OFF* */
547 VLIB_CLI_COMMAND (del_stream_cli, static) = {
548   .path = "packet-generator delete",
549   .function = del_stream,
550   .short_help = "Delete stream with given name",
551 };
552 /* *INDENT-ON* */
553
554 static clib_error_t *
555 change_stream_parameters (vlib_main_t * vm,
556                           unformat_input_t * input, vlib_cli_command_t * cmd)
557 {
558   pg_main_t *pg = &pg_main;
559   pg_stream_t *s, s_new;
560   u32 stream_index = ~0;
561   clib_error_t *error;
562
563   if (unformat (input, "%U", unformat_hash_vec_string,
564                 pg->stream_index_by_name, &stream_index))
565     ;
566   else
567     return clib_error_create ("expecting stream name; got `%U'",
568                               format_unformat_error, input);
569
570   s = pool_elt_at_index (pg->streams, stream_index);
571   s_new = s[0];
572
573   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
574     {
575       if (unformat_user (input, unformat_pg_stream_parameter, &s_new))
576         ;
577
578       else
579         return clib_error_create ("unknown input `%U'",
580                                   format_unformat_error, input);
581     }
582
583   error = validate_stream (&s_new);
584   if (!error)
585     {
586       s[0] = s_new;
587       pg_stream_change (pg, s);
588     }
589
590   return error;
591 }
592
593 /* *INDENT-OFF* */
594 VLIB_CLI_COMMAND (change_stream_parameters_cli, static) = {
595   .path = "packet-generator configure",
596   .short_help = "Change packet generator stream parameters",
597   .function = change_stream_parameters,
598 };
599 /* *INDENT-ON* */
600
601 static clib_error_t *
602 pg_capture_cmd_fn (vlib_main_t * vm,
603                    unformat_input_t * input, vlib_cli_command_t * cmd)
604 {
605   clib_error_t *error = 0;
606   vnet_main_t *vnm = vnet_get_main ();
607   unformat_input_t _line_input, *line_input = &_line_input;
608   vnet_hw_interface_t *hi = 0;
609   u8 *pcap_file_name = 0;
610   u32 hw_if_index;
611   u32 is_disable = 0;
612   u32 count = ~0;
613
614   if (!unformat_user (input, unformat_line_input, line_input))
615     return 0;
616
617   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
618     {
619       if (unformat (line_input, "%U",
620                     unformat_vnet_hw_interface, vnm, &hw_if_index))
621         {
622           hi = vnet_get_hw_interface (vnm, hw_if_index);
623         }
624
625       else if (unformat (line_input, "pcap %s", &pcap_file_name))
626         ;
627       else if (unformat (line_input, "count %u", &count))
628         ;
629       else if (unformat (line_input, "disable"))
630         is_disable = 1;
631
632       else
633         {
634           error = clib_error_create ("unknown input `%U'",
635                                      format_unformat_error, line_input);
636           goto done;
637         }
638     }
639
640   if (!hi)
641     {
642       error = clib_error_return (0, "Please specify interface name");
643       goto done;
644     }
645
646   if (hi->dev_class_index != pg_dev_class.index)
647     {
648       error =
649         clib_error_return (0, "Please specify packet-generator interface");
650       goto done;
651     }
652
653   if (!pcap_file_name && is_disable == 0)
654     {
655       error = clib_error_return (0, "Please specify pcap file name");
656       goto done;
657     }
658
659
660   pg_capture_args_t _a, *a = &_a;
661
662   a->hw_if_index = hw_if_index;
663   a->dev_instance = hi->dev_instance;
664   a->is_enabled = !is_disable;
665   a->pcap_file_name = (char *) pcap_file_name;
666   a->count = count;
667
668   error = pg_capture (a);
669
670 done:
671   unformat_free (line_input);
672
673   return error;
674 }
675
676 /* *INDENT-OFF* */
677 VLIB_CLI_COMMAND (pg_capture_cmd, static) = {
678   .path = "packet-generator capture",
679   .short_help = "packet-generator capture <interface name> pcap <filename> [count <n>]",
680   .function = pg_capture_cmd_fn,
681 };
682 /* *INDENT-ON* */
683
684 static clib_error_t *
685 create_pg_if_cmd_fn (vlib_main_t * vm,
686                      unformat_input_t * input, vlib_cli_command_t * cmd)
687 {
688   pg_main_t *pg = &pg_main;
689   unformat_input_t _line_input, *line_input = &_line_input;
690   u32 if_id, gso_enabled = 0, gso_size = 0, coalesce_enabled = 0;
691   clib_error_t *error = NULL;
692
693   if (!unformat_user (input, unformat_line_input, line_input))
694     return 0;
695
696   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
697     {
698       if (unformat (line_input, "interface pg%u", &if_id))
699         ;
700       else if (unformat (line_input, "coalesce-enabled"))
701         coalesce_enabled = 1;
702       else if (unformat (line_input, "gso-enabled"))
703         {
704           gso_enabled = 1;
705           if (unformat (line_input, "gso-size %u", &gso_size))
706             ;
707           else
708             {
709               error = clib_error_create ("gso enabled but gso size missing");
710               goto done;
711             }
712         }
713       else
714         {
715           error = clib_error_create ("unknown input `%U'",
716                                      format_unformat_error, line_input);
717           goto done;
718         }
719     }
720
721   pg_interface_add_or_get (pg, if_id, gso_enabled, gso_size, coalesce_enabled,
722                            PG_MODE_ETHERNET);
723
724 done:
725   unformat_free (line_input);
726
727   return error;
728 }
729
730 /* *INDENT-OFF* */
731 VLIB_CLI_COMMAND (create_pg_if_cmd, static) = {
732   .path = "create packet-generator",
733   .short_help = "create packet-generator interface <interface name>"
734                 " [gso-enabled gso-size <size> [coalesce-enabled]]",
735   .function = create_pg_if_cmd_fn,
736 };
737 /* *INDENT-ON* */
738
739 /* Dummy init function so that we can be linked in. */
740 static clib_error_t *
741 pg_cli_init (vlib_main_t * vm)
742 {
743   return 0;
744 }
745
746 VLIB_INIT_FUNCTION (pg_cli_init);
747
748 /*
749  * fd.io coding-style-patch-verification: ON
750  *
751  * Local Variables:
752  * eval: (c-set-style "gnu")
753  * End:
754  */