Rewrite vppctl in C
[vpp.git] / src / vpp / app / vppctl.c
1 /*
2  * Copyright (c) 2017 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 <sys/socket.h>
17 #include <sys/un.h>
18 #include <sys/epoll.h>
19 #include <sys/ioctl.h>
20 #include <signal.h>
21 #include <termios.h>
22 #include <unistd.h>
23 #include <string.h>
24
25 #define DEBUG 0
26
27 #if DEBUG
28 #define TELCMDS
29 #define TELOPTS
30 #endif
31
32 #include <arpa/telnet.h>
33
34 #include <vppinfra/mem.h>
35 #include <vppinfra/format.h>
36 #include <vppinfra/socket.h>
37
38 #define SOCKET_FILE "/run/vpp/cli.sock"
39
40 volatile int window_resized = 0;
41 struct termios orig_tio;
42
43 static void
44 send_ttype (clib_socket_t * s, int is_dumb)
45 {
46   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c",
47                                 IAC, SB, TELOPT_TTYPE,
48                                 0, is_dumb ? "dumb" : getenv ("TERM"),
49                                 IAC, SE);
50   clib_socket_tx (s);
51 }
52
53 static void
54 send_naws (clib_socket_t * s)
55 {
56   struct winsize ws;
57
58   if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
59     {
60       clib_unix_warning ("ioctl(TIOCGWINSZ)");
61       return;
62     }
63
64   clib_socket_tx_add_formatted (s, "%c%c%c" "%c%c%c%c" "%c%c",
65                                 IAC, SB, TELOPT_NAWS,
66                                 ws.ws_col >> 8, ws.ws_col & 0xff,
67                                 ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE);
68   clib_socket_tx (s);
69 }
70
71 static void
72 signal_handler_winch (int signum)
73 {
74   window_resized = 1;
75 }
76
77 static void
78 signal_handler_term (int signum)
79 {
80   tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
81 }
82
83 static u8 *
84 process_input (u8 * str, clib_socket_t * s, int is_interactive)
85 {
86   int i = 0;
87
88   while (i < vec_len (s->rx_buffer))
89     {
90       if (s->rx_buffer[i] == IAC)
91         {
92           if (s->rx_buffer[i + 1] == SB)
93             {
94               u8 *sb = 0;
95               char opt = s->rx_buffer[i + 2];
96               i += 3;
97               while (s->rx_buffer[i] != IAC)
98                 vec_add1 (sb, s->rx_buffer[i++]);
99
100 #if DEBUG
101               clib_warning ("SB %s\n  %U", TELOPT (opt),
102                             format_hexdump, sb, vec_len (sb));
103 #endif
104               vec_free (sb);
105               i += 2;
106               if (opt == TELOPT_TTYPE)
107                 send_ttype (s, !is_interactive);
108               else if (is_interactive && opt == TELOPT_NAWS)
109                 send_naws (s);
110             }
111           else
112             {
113 #if DEBUG
114               clib_warning ("IAC at %d, IAC %s %s", i,
115                             TELCMD (s->rx_buffer[i + 1]),
116                             TELOPT (s->rx_buffer[i + 2]));
117 #endif
118               i += 3;
119             }
120         }
121       else
122         vec_add1 (str, s->rx_buffer[i++]);
123     }
124   vec_reset_length (s->rx_buffer);
125   return str;
126 }
127
128
129 int
130 main (int argc, char *argv[])
131 {
132   clib_socket_t _s = { 0 }, *s = &_s;
133   clib_error_t *error = 0;
134   struct epoll_event event;
135   struct sigaction sa;
136   struct termios tio;
137   int efd = -1;
138   u8 *str = 0;
139   u8 *cmd = 0;
140   int do_quit = 0;
141
142
143   clib_mem_init (0, 64ULL << 10);
144
145   /* process command line */
146   argc--;
147   argv++;
148
149   if (argc > 1 && strcmp (argv[0], "-s") == 0)
150     {
151       s->config = argv[1];
152       argc -= 2;
153       argv += 2;
154     }
155   else
156     s->config = SOCKET_FILE;
157
158   while (argc--)
159     cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0);
160
161   s->flags = SOCKET_IS_CLIENT;
162
163   error = clib_socket_init (s);
164   if (error)
165     goto done;
166
167   /* Capture terminal resize events */
168   memset (&sa, 0, sizeof (struct sigaction));
169   sa.sa_handler = signal_handler_winch;
170
171   if (sigaction (SIGWINCH, &sa, 0) < 0)
172     {
173       error = clib_error_return_unix (0, "sigaction");
174       goto done;
175     }
176
177   sa.sa_handler = signal_handler_term;
178   if (sigaction (SIGTERM, &sa, 0) < 0)
179     {
180       error = clib_error_return_unix (0, "sigaction");
181       goto done;
182     }
183
184   /* Save the original tty state so we can restore it later */
185   tcgetattr (STDIN_FILENO, &orig_tio);
186
187   /* Tweak the tty settings */
188   tio = orig_tio;
189   /* echo off, canonical mode off, ext'd input processing off */
190   tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
191   tio.c_cc[VMIN] = 1;           /* 1 byte at a time */
192   tio.c_cc[VTIME] = 0;          /* no timer */
193   tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio);
194
195   efd = epoll_create1 (0);
196
197   /* register STDIN */
198   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
199   event.data.fd = STDIN_FILENO;
200   if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
201     {
202       error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO);
203       goto done;
204     }
205
206   /* register socket */
207   event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
208   event.data.fd = s->fd;
209   if (epoll_ctl (efd, EPOLL_CTL_ADD, s->fd, &event) != 0)
210     {
211       error = clib_error_return_unix (0, "epoll_ctl[%d]", s->fd);
212       goto done;
213     }
214
215   while (1)
216     {
217       int n;
218
219       if (window_resized)
220         {
221           window_resized = 0;
222           send_naws (s);
223         }
224
225       if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
226         {
227           /* maybe we received signal */
228           if (errno == EINTR)
229             continue;
230
231           error = clib_error_return_unix (0, "epoll_wait");
232           goto done;
233         }
234
235       if (n == 0)
236         continue;
237
238       if (event.data.fd == STDIN_FILENO)
239         {
240           int n;
241           char c[100];
242
243           n = read (STDIN_FILENO, c, sizeof (c));
244           if (n > 0)
245             {
246               memcpy (clib_socket_tx_add (s, n), c, n);
247               error = clib_socket_tx (s);
248               if (error)
249                 goto done;
250             }
251           else if (n < 0)
252             clib_warning ("read rv=%d", n);
253         }
254       else if (event.data.fd == s->fd)
255         {
256           error = clib_socket_rx (s, 100);
257           if (error)
258             break;
259
260           if (clib_socket_rx_end_of_file (s))
261             break;
262
263           str = process_input (str, s, cmd == 0);
264
265           if (vec_len (str) > 0)
266             {
267               n = write (STDOUT_FILENO, str, vec_len (str));
268               if (n < 0)
269                 {
270                   error = clib_error_return_unix (0, "write");
271                   goto done;
272                 }
273               vec_reset_length (str);
274             }
275
276           if (do_quit)
277             {
278               clib_socket_tx_add_formatted (s, "q\n");
279               clib_socket_tx (s);
280               do_quit = 0;
281             }
282           if (cmd)
283             {
284               clib_socket_tx_add_formatted (s, "%s\n", cmd);
285               clib_socket_tx (s);
286               vec_free (cmd);
287               do_quit = 1;
288             }
289         }
290       else
291         {
292           error = clib_error_return (0, "unknown fd");
293           goto done;
294         }
295     }
296
297   error = clib_socket_close (s);
298
299 done:
300   vec_free (cmd);
301   vec_free (str);
302   if (efd > -1)
303     close (efd);
304
305   if (error)
306     {
307       clib_error_report (error);
308       return 1;
309     }
310   tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
311   return 0;
312 }
313
314 /* *INDENT-ON* */
315
316 /*
317  * fd.io coding-style-patch-verification: ON
318  *
319  * Local Variables:
320  * eval: (c-set-style "gnu")
321  * End:
322  */