vat2: fix argument parsing
[vpp.git] / src / vat2 / main.c
1 /*
2  * Copyright (c) 2020 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 #include <stdio.h>
17 #include <stdlib.h>
18 #include <stdbool.h>
19 #include <ctype.h>
20 #include <getopt.h>
21 #include <string.h>
22 #include <vlib/vlib.h>
23 #include <vlibapi/api_types.h>
24 #include <vppinfra/hash.h>
25 #include <vppinfra/cJSON.h>
26
27 /* VPP API client includes */
28 #include <vpp-api/client/vppapiclient.h>
29
30 #include <limits.h>
31 #include "vat2.h"
32
33 /*
34  * Filter these messages as they are used to manage the API connection to VPP
35  */
36 char *filter_messages_strings[] = { "memclnt_create",
37                                     "memclnt_delete",
38                                     "sockclnt_create",
39                                     "sockclnt_delete",
40                                     "memclnt_rx_thread_suspend",
41                                     "memclnt_read_timeout",
42                                     "rx_thread_exit",
43                                     "trace_plugin_msg_ids",
44                                     0 };
45
46 static bool
47 filter_message (char *msgname)
48 {
49   char **p = filter_messages_strings;
50
51   while (*p)
52     {
53       if (strcmp (*p, msgname) == 0)
54         return true;
55       p++;
56     }
57   return false;
58 }
59
60 uword *function_by_name;
61 bool debug = false;
62
63 char *vat2_plugin_path;
64 static void
65 vat2_find_plugin_path ()
66 {
67   char *p, path[PATH_MAX];
68   int rv;
69   u8 *s;
70
71   /* find executable path */
72   if ((rv = readlink ("/proc/self/exe", path, PATH_MAX - 1)) == -1)
73     return;
74
75   /* readlink doesn't provide null termination */
76   path[rv] = 0;
77
78   /* strip filename */
79   if ((p = strrchr (path, '/')) == 0)
80     return;
81   *p = 0;
82
83   /* strip bin/ */
84   if ((p = strrchr (path, '/')) == 0)
85     return;
86   *p = 0;
87
88   s = format (0, "%s/" CLIB_LIB_DIR "/vat2_plugins", path, path);
89   vec_add1 (s, 0);
90   vat2_plugin_path = (char *) s;
91 }
92
93 void
94 vac_callback (unsigned char *data, int len)
95 {
96   u16 result_msg_id = ntohs(*((u16 *)data));
97   DBG("Received something async: %d\n", result_msg_id);
98 }
99
100 int vat2_load_plugins (char *path, char *filter, int *loaded);
101
102 static int
103 register_function (void)
104 {
105   int loaded;
106
107   vat2_find_plugin_path();
108   DBG("Plugin Path %s\n", vat2_plugin_path);
109   int rv = vat2_load_plugins(vat2_plugin_path, 0, &loaded);
110   DBG("Loaded %u plugins\n", loaded);
111   return rv;
112 }
113
114 struct apifuncs_s
115 {
116   cJSON (*f) (cJSON *);
117   cJSON (*tojson) (void *);
118   u32 crc;
119 };
120
121 struct apifuncs_s *apifuncs = 0;
122
123 void
124 vat2_register_function (char *name, cJSON (*f) (cJSON *),
125                         cJSON (*tojson) (void *), u32 crc)
126 {
127   struct apifuncs_s funcs = { .f = f, .tojson = tojson, .crc = crc };
128   vec_add1 (apifuncs, funcs);
129   hash_set_mem (function_by_name, name, vec_len (apifuncs) - 1);
130 }
131
132 static int
133 vat2_exec_command_by_name (char *msgname, cJSON *o)
134 {
135   u32 crc = 0;
136   if (filter_message (msgname))
137     return 0;
138
139   cJSON *crc_obj = cJSON_GetObjectItem (o, "_crc");
140   if (crc_obj)
141     {
142       char *crc_str = cJSON_GetStringValue (crc_obj);
143       crc = (u32) strtol (crc_str, NULL, 16);
144     }
145
146   uword *p = hash_get_mem (function_by_name, msgname);
147   if (!p)
148     {
149       fprintf (stderr, "No such command %s\n", msgname);
150       return -1;
151     }
152   if (crc && crc != apifuncs[p[0]].crc)
153     {
154       fprintf (stderr, "API CRC does not match: %s!\n", msgname);
155     }
156
157   cJSON *(*fp) (cJSON *);
158   fp = (void *) apifuncs[p[0]].f;
159   cJSON *r = (*fp) (o);
160
161   if (r)
162     {
163       char *output = cJSON_Print (r);
164       cJSON_Delete (r);
165       printf ("%s\n", output);
166       free (output);
167     }
168   else
169     {
170       fprintf (stderr, "Call failed: %s\n", msgname);
171       return -1;
172     }
173   return 0;
174 }
175
176 static int
177 vat2_exec_command (cJSON *o)
178 {
179
180   cJSON *msg_id_obj = cJSON_GetObjectItem (o, "_msgname");
181   if (!msg_id_obj)
182     {
183       fprintf (stderr, "Missing '_msgname' element!\n");
184       return -1;
185     }
186
187   char *name = cJSON_GetStringValue (msg_id_obj);
188
189   return vat2_exec_command_by_name (name, o);
190 }
191
192 static void
193 print_template (char *msgname)
194 {
195   uword *p = hash_get_mem (function_by_name, msgname);
196   if (!p)
197     goto error;
198
199   cJSON *(*fp) (void *);
200   fp = (void *) apifuncs[p[0]].tojson;
201   if (!fp)
202     goto error;
203
204   void *scratch = malloc (2048);
205   if (!scratch)
206     goto error;
207
208   memset (scratch, 0, 2048);
209   cJSON *t = fp (scratch);
210   if (!t)
211     goto error;
212   free (scratch);
213   char *output = cJSON_Print (t);
214   if (!output)
215     goto error;
216
217   cJSON_Delete (t);
218   printf ("%s\n", output);
219   free (output);
220
221   return;
222
223 error:
224   fprintf (stderr, "error printing template for: %s\n", msgname);
225 }
226
227 static void
228 dump_apis (void)
229 {
230   char *name;
231   u32 *i;
232   hash_foreach_mem (name, i, function_by_name, ({ printf ("%s\n", name); }));
233 }
234
235 static void
236 print_help (void)
237 {
238   char *help_string =
239     "Usage: vat2 [OPTION] <message-name> <JSON object>\n"
240     "Send API message to VPP and print reply\n"
241     "\n"
242     "-d, --debug                    Print additional information\n"
243     "-p, --prefix <prefix>          Specify shared memory prefix to connect "
244     "to a given VPP instance\n"
245     "-f, --file <filename>          File containing a JSON object with the "
246     "arguments for the message to send\n"
247     "-t, --template <message-name>  Print a template JSON object for given API"
248     " message\n"
249     "--dump-apis                    List all APIs available in VAT2 (might "
250     "not reflect running VPP)\n"
251     "\n";
252   printf ("%s", help_string);
253 }
254
255 int main (int argc, char **argv)
256 {
257   /* Create a heap of 64MB */
258   clib_mem_init (0, 64 << 20);
259   char *filename = 0, *prefix = 0, *template = 0;
260   int index;
261   int c;
262   opterr = 0;
263   cJSON *o = 0;
264   int option_index = 0;
265   bool dump_api = false;
266   bool debug = false;
267   char *msgname = 0;
268   static struct option long_options[] = {
269     { "debug", no_argument, 0, 'd' },
270     { "prefix", required_argument, 0, 'p' },
271     { "file", required_argument, 0, 'f' },
272     { "dump-apis", no_argument, 0, 0 },
273     { "template", required_argument, 0, 't' },
274     { 0, 0, 0, 0 }
275   };
276
277   while ((c = getopt_long (argc, argv, "hdp:f:t:", long_options,
278                            &option_index)) != -1)
279     {
280       switch (c)
281         {
282         case 0:
283           if (option_index == 3)
284             dump_api = true;
285           break;
286         case 'd':
287           debug = true;
288           break;
289         case 't':
290           template = optarg;
291           break;
292         case 'p':
293           prefix = optarg;
294           break;
295         case 'f':
296           filename = optarg;
297           break;
298         case '?':
299           print_help ();
300           return 1;
301         default:
302           abort ();
303         }
304     }
305   DBG ("debug = %d, filename = %s, template = %s, shared memory prefix: %s\n",
306        debug, filename, template, prefix);
307
308   for (index = optind; index < argc; index++)
309     DBG ("Non-option argument %s\n", argv[index]);
310
311   index = optind;
312
313   if (argc > index + 2)
314     {
315       fprintf (stderr, "%s: Too many arguments\n", argv[0]);
316       exit (-1);
317     }
318
319   /* Load plugins */
320   function_by_name = hash_create_string (0, sizeof (uword));
321   int res = register_function();
322   if (res < 0) {
323     fprintf(stderr, "%s: loading plugins failed\n", argv[0]);
324     exit(-1);
325   }
326
327   if (template)
328     {
329       print_template (template);
330       exit (0);
331     }
332
333   if (dump_api)
334     {
335       dump_apis ();
336       exit (0);
337     }
338
339   /* Read message arguments from command line */
340   if (argc >= (index + 1))
341     {
342       msgname = argv[index];
343     }
344   if (argc == (index + 2)) {
345     o = cJSON_Parse(argv[index+1]);
346     if (!o) {
347       fprintf(stderr, "%s: Failed parsing JSON input: %s\n", argv[0], cJSON_GetErrorPtr());
348       exit(-1);
349     }
350   }
351
352   if (!msgname && !filename)
353     {
354       print_help ();
355       exit (-1);
356     }
357
358   /* Read message from file */
359   if (filename) {
360       if (argc > index)
361         {
362           fprintf (stderr, "%s: Superfluous arguments when filename given\n",
363                    argv[0]);
364           exit (-1);
365         }
366
367     FILE *f = fopen(filename, "r");
368     size_t chunksize, bufsize;
369     size_t n_read = 0;
370     size_t n;
371
372     if (!f) {
373       fprintf(stderr, "%s: can't open file: %s\n", argv[0], filename);
374       exit(-1);
375     }
376
377     chunksize = bufsize = 1024;
378     char *buf = malloc(bufsize);
379     while ((n = fread (buf + n_read, 1, chunksize, f)))
380       {
381         n_read += n;
382         if (n == chunksize)
383           {
384             bufsize += chunksize;
385             buf = realloc (buf, bufsize);
386           }
387       }
388     fclose(f);
389     if (n_read) {
390       o = cJSON_Parse(buf);
391       if (!o) {
392         fprintf(stderr, "%s: Failed parsing JSON input: %s\n", argv[0], cJSON_GetErrorPtr());
393         exit(-1);
394       }
395     }
396     free (buf);
397   }
398
399   if (!o)
400     {
401       fprintf (stderr, "%s: Failed parsing JSON input\n", argv[0]);
402       exit (-1);
403     }
404
405   if (vac_connect ("vat2", prefix, 0, 1024))
406     {
407       fprintf (stderr, "Failed connecting to VPP\n");
408       exit (-1);
409     }
410
411   if (msgname)
412     {
413       vat2_exec_command_by_name (msgname, o);
414     }
415   else
416     {
417       if (cJSON_IsArray (o))
418         {
419           size_t size = cJSON_GetArraySize (o);
420           for (int i = 0; i < size; i++)
421             vat2_exec_command (cJSON_GetArrayItem (o, i));
422         }
423     }
424   cJSON_Delete (o);
425   vac_disconnect();
426   exit (0);
427
428 }