| /* |
| * Copyright (c) 2015 Cisco and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "vat.h" |
| #include <dlfcn.h> |
| #include "plugin.h" |
| #include <signal.h> |
| #include <limits.h> |
| |
| vat_main_t vat_main; |
| |
| #include <vlibapi/api_helper_macros.h> |
| |
| void |
| vat_suspend (vlib_main_t * vm, f64 interval) |
| { |
| /* do nothing in the standalone version, just return */ |
| } |
| |
| int |
| connect_to_vpe (char *name) |
| { |
| vat_main_t *vam = &vat_main; |
| api_main_t *am = vlibapi_get_main (); |
| |
| if (vl_client_connect_to_vlib ("/vpe-api", name, 32) < 0) |
| return -1; |
| |
| vam->vl_input_queue = am->shmem_hdr->vl_input_queue; |
| vam->my_client_index = am->my_client_index; |
| |
| return 0; |
| } |
| |
| |
| vlib_global_main_t vlib_global_main; |
| |
| void |
| vlib_cli_output (struct vlib_main_t *vm, char *fmt, ...) |
| { |
| clib_warning ("BUG"); |
| } |
| |
| static u8 * |
| format_api_error (u8 * s, va_list * args) |
| { |
| vat_main_t *vam = va_arg (*args, vat_main_t *); |
| i32 error = va_arg (*args, u32); |
| uword *p; |
| |
| p = hash_get (vam->error_string_by_error_number, -error); |
| |
| if (p) |
| s = format (s, "%s", p[0]); |
| else |
| s = format (s, "%d", error); |
| return s; |
| } |
| |
| void |
| do_one_file (vat_main_t * vam) |
| { |
| int rv; |
| int (*fp) (vat_main_t * vam); |
| int arg_len; |
| unformat_input_t _input; |
| u8 *cmdp, *argsp; |
| uword *p; |
| u8 *this_cmd = 0; |
| |
| vam->input = &_input; |
| |
| /* Used by the "quit" command handler */ |
| if (setjmp (vam->jump_buf) != 0) |
| return; |
| |
| vam->jump_buf_set = 1; |
| |
| while (1) |
| { |
| if (vam->ifp == stdin) |
| { |
| if (vam->exec_mode == 0) |
| rv = write (1, "vat# ", 5); |
| else |
| rv = write (1, "exec# ", 6); |
| } |
| |
| vec_set_len (vam->inbuf, 4096); |
| |
| if (vam->do_exit || |
| fgets ((char *) vam->inbuf, vec_len (vam->inbuf), vam->ifp) == 0) |
| break; |
| |
| vam->input_line_number++; |
| |
| vec_free (this_cmd); |
| |
| this_cmd = |
| (u8 *) clib_macro_eval (&vam->macro_main, (i8 *) vam->inbuf, |
| 1 /* complain */ , |
| 0 /* level */ , |
| 8 /* max_level */ ); |
| |
| if (vam->exec_mode == 0) |
| { |
| /* Split input into cmd + args */ |
| cmdp = this_cmd; |
| |
| while (cmdp < (this_cmd + vec_len (this_cmd))) |
| { |
| if (*cmdp == ' ' || *cmdp == '\t' || *cmdp == '\n') |
| { |
| cmdp++; |
| } |
| else |
| break; |
| } |
| argsp = cmdp; |
| while (argsp < (this_cmd + vec_len (this_cmd))) |
| { |
| if (*argsp != ' ' && *argsp != '\t' && *argsp != '\n') |
| { |
| argsp++; |
| } |
| else |
| break; |
| } |
| *argsp++ = 0; |
| while (argsp < (this_cmd + vec_len (this_cmd))) |
| { |
| if (*argsp == ' ' || *argsp == '\t' || *argsp == '\n') |
| { |
| argsp++; |
| } |
| else |
| break; |
| } |
| |
| |
| /* Blank input line? */ |
| if (*cmdp == 0) |
| continue; |
| |
| p = hash_get_mem (vam->function_by_name, cmdp); |
| if (p == 0) |
| { |
| errmsg ("'%s': function not found\n", cmdp); |
| continue; |
| } |
| |
| arg_len = strlen ((char *) argsp); |
| |
| unformat_init_string (vam->input, (char *) argsp, arg_len); |
| fp = (void *) p[0]; |
| } |
| else |
| { |
| unformat_init_string (vam->input, (char *) this_cmd, |
| strlen ((char *) this_cmd)); |
| cmdp = this_cmd; |
| fp = exec; |
| } |
| |
| rv = (*fp) (vam); |
| if (rv < 0) |
| errmsg ("%s error: %U\n", cmdp, format_api_error, vam, rv); |
| unformat_free (vam->input); |
| |
| if (vam->regenerate_interface_table) |
| { |
| vam->regenerate_interface_table = 0; |
| vam->api_sw_interface_dump (vam); |
| } |
| |
| /* Hack to pick up new client index after memfd_segment_create pivot */ |
| if (vam->client_index_invalid) |
| { |
| vat_main_t *vam = &vat_main; |
| api_main_t *am = vlibapi_get_main (); |
| |
| vam->vl_input_queue = am->shmem_hdr->vl_input_queue; |
| vam->my_client_index = am->my_client_index; |
| vam->client_index_invalid = 0; |
| } |
| } |
| } |
| |
| static void |
| init_error_string_table (vat_main_t * vam) |
| { |
| |
| vam->error_string_by_error_number = hash_create (0, sizeof (uword)); |
| |
| #define _(n,v,s) hash_set (vam->error_string_by_error_number, -v, s); |
| foreach_vnet_api_error; |
| #undef _ |
| |
| hash_set (vam->error_string_by_error_number, 99, "Misc"); |
| } |
| |
| static i8 * |
| eval_current_file (clib_macro_main_t * mm, i32 complain) |
| { |
| vat_main_t *vam = &vat_main; |
| return ((i8 *) format (0, "%s%c", vam->current_file, 0)); |
| } |
| |
| static i8 * |
| eval_current_line (clib_macro_main_t * mm, i32 complain) |
| { |
| vat_main_t *vam = &vat_main; |
| return ((i8 *) format (0, "%d%c", vam->input_line_number, 0)); |
| } |
| |
| static void |
| signal_handler (int signum, siginfo_t * si, ucontext_t * uc) |
| { |
| vat_main_t *vam = &vat_main; |
| |
| switch (signum) |
| { |
| /* these (caught) signals cause the application to exit */ |
| case SIGINT: |
| case SIGTERM: |
| if (vam->jump_buf_set) |
| { |
| vam->do_exit = 1; |
| return; |
| } |
| |
| /* FALLTHROUGH on purpose */ |
| |
| default: |
| break; |
| } |
| |
| _exit (1); |
| } |
| |
| static void |
| setup_signal_handlers (void) |
| { |
| uword i; |
| struct sigaction sa; |
| |
| for (i = 1; i < 32; i++) |
| { |
| clib_memset (&sa, 0, sizeof (sa)); |
| sa.sa_sigaction = (void *) signal_handler; |
| sa.sa_flags = SA_SIGINFO; |
| |
| switch (i) |
| { |
| /* these signals take the default action */ |
| case SIGABRT: |
| case SIGKILL: |
| case SIGCONT: |
| case SIGSTOP: |
| case SIGUSR1: |
| case SIGUSR2: |
| case SIGPROF: |
| continue; |
| |
| /* ignore SIGPIPE, SIGCHLD */ |
| case SIGPIPE: |
| case SIGCHLD: |
| case SIGWINCH: |
| sa.sa_sigaction = (void *) SIG_IGN; |
| break; |
| |
| /* catch and handle all other signals */ |
| default: |
| break; |
| } |
| |
| if (sigaction (i, &sa, 0) < 0) |
| clib_unix_warning ("sigaction %U", format_signal, i); |
| } |
| } |
| |
| static void |
| vat_find_plugin_path () |
| { |
| char *p, path[PATH_MAX]; |
| int rv; |
| u8 *s; |
| |
| /* find executable path */ |
| if ((rv = readlink ("/proc/self/exe", path, PATH_MAX - 1)) == -1) |
| return; |
| |
| /* readlink doesn't provide null termination */ |
| path[rv] = 0; |
| |
| /* strip filename */ |
| if ((p = strrchr (path, '/')) == 0) |
| return; |
| *p = 0; |
| |
| /* strip bin/ */ |
| if ((p = strrchr (path, '/')) == 0) |
| return; |
| *p = 0; |
| |
| s = format (0, "%s/" CLIB_LIB_DIR "/vpp_api_test_plugins", path, path); |
| vec_add1 (s, 0); |
| vat_plugin_path = (char *) s; |
| } |
| |
| static void |
| load_features (void) |
| { |
| vat_registered_features_t *f; |
| vat_main_t *vam = &vat_main; |
| clib_error_t *error; |
| |
| f = vam->feature_function_registrations; |
| |
| while (f) |
| { |
| error = f->function (vam); |
| if (error) |
| { |
| clib_warning ("INIT FAILED"); |
| } |
| f = f->next; |
| } |
| } |
| |
| static inline clib_error_t * |
| call_init_exit_functions_internal (vlib_main_t *vm, |
| _vlib_init_function_list_elt_t **headp, |
| int call_once, int do_sort, int is_global) |
| { |
| vlib_global_main_t *vgm = vlib_get_global_main (); |
| clib_error_t *error = 0; |
| _vlib_init_function_list_elt_t *i; |
| |
| ASSERT (is_global == 1); |
| |
| #if 0 |
| /* Not worth copying the topological sort code */ |
| if (do_sort && (error = vlib_sort_init_exit_functions (headp))) |
| return (error); |
| #endif |
| |
| i = *headp; |
| while (i) |
| { |
| if (call_once && !hash_get (vgm->init_functions_called, i->f)) |
| { |
| if (call_once) |
| hash_set1 (vgm->init_functions_called, i->f); |
| error = i->f (vm); |
| if (error) |
| return error; |
| } |
| i = i->next_init_function; |
| } |
| return error; |
| } |
| |
| clib_error_t * |
| vlib_call_init_exit_functions (vlib_main_t *vm, |
| _vlib_init_function_list_elt_t **headp, |
| int call_once, int is_global) |
| { |
| return call_init_exit_functions_internal (vm, headp, call_once, |
| 1 /* do_sort */, is_global); |
| } |
| |
| static void |
| vat_register_interface_dump (vat_main_t *vam) |
| { |
| void *handle; |
| plugin_info_t *pi; |
| |
| vec_foreach (pi, vat_plugin_main.plugin_info) |
| { |
| handle = dlsym (pi->handle, "api_sw_interface_dump"); |
| if (handle) |
| { |
| vam->api_sw_interface_dump = handle; |
| break; |
| } |
| } |
| |
| if (!vam->api_sw_interface_dump) |
| { |
| fformat (stderr, |
| "sw_interface_dump not found in interface_test plugin!\n"); |
| exit (1); |
| } |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| vlib_global_main_t *vgm = vlib_get_global_main (); |
| vat_main_t *vam = &vat_main; |
| unformat_input_t _argv, *a = &_argv; |
| u8 **input_files = 0; |
| u8 *output_file = 0; |
| u8 *chroot_prefix; |
| u8 *this_input_file; |
| u8 interactive = 1; |
| u8 json_output = 0; |
| int i; |
| f64 timeout; |
| clib_error_t *error; |
| vlib_main_t *vm; |
| |
| clib_mem_init_thread_safe (0, 128 << 20); |
| vlib_main_init (); |
| vm = vlib_get_first_main (); |
| |
| clib_macro_init (&vam->macro_main); |
| clib_macro_add_builtin (&vam->macro_main, "current_file", |
| eval_current_file); |
| clib_macro_add_builtin (&vam->macro_main, "current_line", |
| eval_current_line); |
| |
| init_error_string_table (vam); |
| vec_validate (vam->cmd_reply, 0); |
| vec_reset_length (vam->cmd_reply); |
| |
| vat_find_plugin_path (); |
| |
| unformat_init_command_line (a, argv); |
| |
| while (unformat_check_input (a) != UNFORMAT_END_OF_INPUT) |
| { |
| if (unformat (a, "in %s", &this_input_file)) |
| vec_add1 (input_files, this_input_file); |
| else if (unformat (a, "out %s", &output_file)) |
| ; |
| else if (unformat (a, "script")) |
| interactive = 0; |
| else if (unformat (a, "json")) |
| json_output = 1; |
| else if (unformat (a, "socket-name %s", &vam->socket_name)) |
| ; |
| else if (unformat (a, "default-socket")) |
| { |
| vam->socket_name = format (0, "%s%c", API_SOCKET_FILE, 0); |
| } |
| else if (unformat (a, "plugin_path %s", (u8 *) & vat_plugin_path)) |
| vec_add1 (vat_plugin_path, 0); |
| else if (unformat (a, "plugin_name_filter %s", |
| (u8 *) & vat_plugin_name_filter)) |
| vec_add1 (vat_plugin_name_filter, 0); |
| else if (unformat (a, "chroot prefix %s", &chroot_prefix)) |
| { |
| vl_set_memory_root_path ((char *) chroot_prefix); |
| } |
| else |
| { |
| fformat |
| (stderr, |
| "%s: usage [in <f1> ... in <fn>] [out <fn>] [script] [json]\n" |
| "[plugin_path <path>][default-socket][socket-name <name>]\n" |
| "[plugin_name_filter <filter>][chroot prefix <path>]\n", |
| argv[0]); |
| exit (1); |
| } |
| } |
| |
| if (output_file) |
| vam->ofp = fopen ((char *) output_file, "w"); |
| else |
| vam->ofp = stdout; |
| |
| if (vam->ofp == NULL) |
| { |
| fformat (stderr, "Couldn't open output file %s\n", |
| output_file ? (char *) output_file : "stdout"); |
| exit (1); |
| } |
| |
| clib_time_init (&vam->clib_time); |
| |
| vat_api_hookup (vam); |
| vat_plugin_api_reference (); |
| |
| setup_signal_handlers (); |
| |
| if (vam->socket_name && vat_socket_connect (vam)) |
| fformat (stderr, "WARNING: socket connection failed"); |
| |
| if ((!vam->socket_client_main || vam->socket_client_main->socket_fd == 0) |
| && connect_to_vpe ("vpp_api_test") < 0) |
| { |
| svm_region_exit (); |
| fformat (stderr, "Couldn't connect to vpe, exiting...\n"); |
| exit (1); |
| } |
| |
| vam->json_output = json_output; |
| |
| vec_validate (vam->inbuf, 4096); |
| |
| load_features (); |
| |
| vam->current_file = (u8 *) "plugin-init"; |
| vat_plugin_init (vam); |
| |
| vat_register_interface_dump (vam); |
| |
| if (!json_output) |
| vam->api_sw_interface_dump (vam); |
| |
| /* Set up the init function hash table */ |
| vgm->init_functions_called = hash_create (0, 0); |
| |
| /* Execute plugin init and api_init functions */ |
| error = vlib_call_init_exit_functions (vm, &vgm->init_function_registrations, |
| 1 /* call once */, 1 /* is_global*/); |
| |
| if (error) |
| clib_error_report (error); |
| |
| error = |
| vlib_call_init_exit_functions (vm, &vgm->api_init_function_registrations, |
| 1 /* call_once */, 1 /* is_global */); |
| |
| if (error) |
| clib_error_report (error); |
| |
| for (i = 0; i < vec_len (input_files); i++) |
| { |
| vam->ifp = fopen ((char *) input_files[i], "r"); |
| if (vam->ifp == NULL) |
| { |
| fformat (stderr, "Couldn't open input file %s\n", input_files[i]); |
| continue; |
| } |
| vam->current_file = input_files[i]; |
| vam->input_line_number = 0; |
| do_one_file (vam); |
| fclose (vam->ifp); |
| } |
| |
| if (output_file) |
| fclose (vam->ofp); |
| |
| if (interactive) |
| { |
| vam->ifp = stdin; |
| vam->ofp = stdout; |
| vam->current_file = (u8 *) "interactive"; |
| do_one_file (vam); |
| fclose (vam->ifp); |
| } |
| |
| /* |
| * Particularly when running a script, don't be in a hurry to leave. |
| * A reply message queued to this process will end up constipating |
| * the allocation rings. |
| */ |
| timeout = vat_time_now (vam) + 2.0; |
| while (vam->result_ready == 0 && vat_time_now (vam) < timeout) |
| ; |
| |
| if (vat_time_now (vam) > timeout) |
| clib_warning ("BUG: message reply spin-wait timeout"); |
| |
| vl_client_disconnect_from_vlib (); |
| exit (0); |
| } |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |